From 300592f8ce8773a656d4e73a19e9288cbae6f3bc Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 14 Feb 2024 19:31:25 +0530 Subject: [PATCH 01/23] feat: Add an api to add bulk import users --- .../java/io/supertokens/inmemorydb/Start.java | 12 +- .../inmemorydb/config/SQLiteConfig.java | 4 + .../inmemorydb/queries/BulkImportQueries.java | 68 +++ .../inmemorydb/queries/GeneralQueries.java | 6 + .../io/supertokens/webserver/Webserver.java | 3 + .../api/bulkimport/AddBulkImportUsers.java | 124 ++++++ .../apis/AddBulkImportUsersTest.java | 408 ++++++++++++++++++ 7 files changed, 624 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/supertokens/inmemorydb/queries/BulkImportQueries.java create mode 100644 src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java create mode 100644 src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 129adee22..2cb372551 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -26,6 +26,8 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.dashboard.DashboardSessionInfo; import io.supertokens.pluginInterface.dashboard.DashboardUser; @@ -102,7 +104,7 @@ public class Start implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage, JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage, UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, TOTPSQLStorage, ActiveUsersStorage, - DashboardSQLStorage, AuthRecipeSQLStorage { + DashboardSQLStorage, AuthRecipeSQLStorage, BulkImportStorage { private static final Object appenderLock = new Object(); private static final String APP_ID_KEY_NAME = "app_id"; @@ -2952,4 +2954,12 @@ public UserIdMapping[] getUserIdMapping_Transaction(TransactionConnection con, A } } + @Override + public void addBulkImportUsers(AppIdentifier appIdentifier, ArrayList users) throws StorageQueryException { + try { + BulkImportQueries.insertBulkImportUsers(this, users); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } } diff --git a/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java b/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java index 25fd59c61..9ce5cf561 100644 --- a/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java +++ b/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java @@ -156,4 +156,8 @@ public String getDashboardUsersTable() { public String getDashboardSessionsTable() { return "dashboard_user_sessions"; } + + public String getBulkImportUsersTable() { + return "bulk_import_users"; + } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/BulkImportQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/BulkImportQueries.java new file mode 100644 index 000000000..aeb6fe9e6 --- /dev/null +++ b/src/main/java/io/supertokens/inmemorydb/queries/BulkImportQueries.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, 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.inmemorydb.queries; + +import io.supertokens.inmemorydb.config.Config; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; + +import static io.supertokens.inmemorydb.PreparedStatementValueSetter.NO_OP_SETTER; +import static io.supertokens.inmemorydb.QueryExecutorTemplate.update; + +import java.sql.SQLException; +import java.util.ArrayList; + +import io.supertokens.inmemorydb.Start; + +public class BulkImportQueries { + static String getQueryToCreateBulkImportUsersTable(Start start) { + return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getBulkImportUsersTable() + " (" + + "id CHAR(36) PRIMARY KEY," + + "raw_data TEXT NOT NULL," + + "status VARCHAR(128) NOT NULL DEFAULT 'NEW'," + + "error_msg TEXT," + + "created_at TIMESTAMP DEFAULT (strftime('%s', 'now'))," + + "updated_at TIMESTAMP DEFAULT (strftime('%s', 'now'))" + + " );"; + } + + public static String getQueryToCreateStatusUpdatedAtIndex(Start start) { + return "CREATE INDEX IF NOT EXISTS bulk_import_users_status_updated_at_index ON " + + Config.getConfig(start).getBulkImportUsersTable() + " (status, updated_at)"; + } + + public static void insertBulkImportUsers(Start start, ArrayList users) + throws SQLException, StorageQueryException { + StringBuilder queryBuilder = new StringBuilder( + "INSERT INTO " + Config.getConfig(start).getBulkImportUsersTable() + " (id, raw_data) VALUES "); + for (BulkImportUser user : users) { + queryBuilder.append("('") + .append(user.id) + .append("', '") + .append(user.toString()) + .append("')"); + + if (user != users.get(users.size() - 1)) { + queryBuilder.append(","); + } + } + queryBuilder.append(";"); + update(start, queryBuilder.toString(), NO_OP_SETTER); + } +} + diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index c645f2f7a..4ac2f32a9 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -407,6 +407,12 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc update(start, TOTPQueries.getQueryToCreateUsedCodesExpiryTimeIndex(start), NO_OP_SETTER); } + if (!doesTableExists(start, Config.getConfig(start).getBulkImportUsersTable())) { + getInstance(main).addState(CREATING_NEW_TABLE, null); + update(start, BulkImportQueries.getQueryToCreateBulkImportUsersTable(start), NO_OP_SETTER); + // index: + update(start, BulkImportQueries.getQueryToCreateStatusUpdatedAtIndex(start), NO_OP_SETTER); + } } diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 8763843e2..1664fffe2 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -27,6 +27,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.api.accountlinking.*; +import io.supertokens.webserver.api.bulkimport.AddBulkImportUsers; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -259,6 +260,8 @@ private void setupRoutes() { addAPI(new RequestStatsAPI(main)); + addAPI(new AddBulkImportUsers(main)); + StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java new file mode 100644 index 000000000..0f98470a1 --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import io.supertokens.Main; +import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.exceptions.InvalidBulkImportDataException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantConfig; +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.util.ArrayList; +import java.util.Arrays; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +public class AddBulkImportUsers extends WebserverAPI { + private static final int MAX_USERS_TO_ADD = 10000; + + public AddBulkImportUsers(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/bulk-import/add-users"; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + JsonArray users = InputParser.parseArrayOrThrowError(input, "users", false); + + if (users.size() > MAX_USERS_TO_ADD) { + JsonObject errorResponseJson = new JsonObject(); + errorResponseJson.addProperty("error", "You can only add 1000 users at a time."); + super.sendJsonResponse(400, errorResponseJson, resp); + return; + } + + AppIdentifier appIdentifier = null; + try { + appIdentifier = getTenantIdentifierFromRequest(req).toAppIdentifier(); + } catch (ServletException e) { + throw new ServletException(e); + } + + TenantConfig[] allTenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main); + ArrayList validTenantIds = new ArrayList<>(); + Arrays.stream(allTenantConfigs) + .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); + + JsonArray errorsJson = new JsonArray(); + ArrayList usersToAdd = new ArrayList<>(); + + for (int i = 0; i < users.size(); i++) { + try { + usersToAdd.add(new BulkImportUser(users.get(i).getAsJsonObject(), validTenantIds, null)); + } catch (InvalidBulkImportDataException e) { + JsonObject errorObj = new JsonObject(); + + JsonArray errors = e.errors.stream() + .map(JsonPrimitive::new) + .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); + + errorObj.addProperty("index", i); + errorObj.add("errors", errors); + + errorsJson.add(errorObj); + } catch (Exception e) { + JsonObject errorObj = new JsonObject(); + errorObj.addProperty("index", i); + errorObj.addProperty("errors", "An unknown error occurred"); + errorsJson.add(errorObj); + } + } + + if (errorsJson.size() > 0) { + JsonObject errorResponseJson = new JsonObject(); + errorResponseJson.addProperty("error", + "Data has missing or invalid fields. Please check the users field for more details."); + errorResponseJson.add("users", errorsJson); + super.sendJsonResponse(400, errorResponseJson, resp); + return; + } + + try { + AppIdentifierWithStorage appIdentifierWithStorage = getAppIdentifierWithStorage(req); + BulkImportStorage storage = appIdentifierWithStorage.getBulkImportStorage(); + storage.addBulkImportUsers(appIdentifierWithStorage, usersToAdd); + } catch (Exception e) { + throw new ServletException(e); + } + + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + super.sendJsonResponse(200, result, resp); + + } +} diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java new file mode 100644 index 000000000..abc5d1aa0 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2024, 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.bulkimport.apis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.UUID; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +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; + +public class AddBulkImportUsersTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + public String getResponseMessageFromError(String response) { + return response.substring(response.indexOf("Message: ") + "Message: ".length()); + } + + @Test + public void shouldThrow400Error() 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 genericErrMsg = "Data has missing or invalid fields. Please check the users field for more details."; + + // users is required in the json body + { + // CASE 1: users field is not present + try { + JsonObject request = new JsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, "Field name 'users' is invalid in JSON input"); + } + // CASE 2: users field type in incorrect + try { + JsonObject request = new JsonParser().parse("{\"users\": \"string\"}").getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, "Field name 'users' is invalid in JSON input"); + } + } + // loginMethod array is required in the user object + { + // CASE 1: loginMethods field is not present + try { + JsonObject request = new JsonParser().parse("{\"users\":[{}]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"loginMethods is required.\"]}]}"); + } + // CASE 2: loginMethods field type in incorrect + try { + JsonObject request = new JsonParser().parse("{\"users\":[{\"loginMethods\": \"string\"}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"loginMethods should be of type array of object.\"]}]}"); + } + // CASE 3: loginMethods array is empty + try { + JsonObject request = new JsonParser().parse("{\"users\":[{\"loginMethods\": []}]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"At least one loginMethod is required.\"]}]}"); + } + } + // Invalid field type of non required fields outside loginMethod + { + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"externalUserId\":[],\"userMetaData\":[],\"roles\":{},\"totp\":{}}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"externalUserId should be of type string.\",\"roles should be of type array of string.\",\"totp should be of type array of object.\",\"loginMethods is required.\"]}]}"); + } + } + // Invalid field type of non required fields inside loginMethod + { + try { + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"recipeId\":[],\"tenantId\":[],\"isPrimary\":[],\"isVerified\":[],\"timeJoinedInMSSinceEpoch\":[]}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"recipeId should be of type string for a loginMethod.\",\"tenantId should be of type string for a loginMethod.\",\"isVerified should be of type boolean for a loginMethod.\",\"isPrimary should be of type boolean for a loginMethod.\",\"timeJoinedInMSSinceEpoch should be of type number for a loginMethod\"]}]}"); + } + } + // Invalid recipeId + { + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"invalid_recipe_id\"}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!\"]}]}"); + } + } + // Invalid field type in emailpassword recipe + { + // CASE 1: email, passwordHash and hashingAlgorithm are not present + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\"}]}]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"email is required for an emailpassword recipe.\",\"passwordHash is required for an emailpassword recipe.\",\"hashingAlgorithm is required for an emailpassword recipe.\"]}]}"); + } + // CASE 2: email, passwordHash and hashingAlgorithm field type is incorrect + try { + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":[],\"passwordHash\":[],\"hashingAlgorithm\":[]}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"email should be of type string for an emailpassword recipe.\",\"passwordHash should be of type string for an emailpassword recipe.\",\"hashingAlgorithm should be of type string for an emailpassword recipe.\"]}]}"); + } + // CASE 3: hashingAlgorithm is not one of bcrypt, argon2, firebase_scrypt + try { + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"somehash\",\"hashingAlgorithm\":\"invalid_algorithm\"}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!\"]}]}"); + } + } + // Invalid field type in thirdparty recipe + { + // CASE 1: email, thirdPartyId and thirdPartyUserId are not present + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"thirdparty\"}]}]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"email is required for a thirdparty recipe.\",\"thirdPartyId is required for a thirdparty recipe.\",\"thirdPartyUserId is required for a thirdparty recipe.\"]}]}"); + } + // CASE 2: email, passwordHash and thirdPartyUserId field type is incorrect + try { + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"thirdparty\",\"email\":[],\"thirdPartyId\":[],\"thirdPartyUserId\":[]}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"email should be of type string for a thirdparty recipe.\",\"thirdPartyId should be of type string for a thirdparty recipe.\",\"thirdPartyUserId should be of type string for a thirdparty recipe.\"]}]}"); + } + } + // Invalid field type in passwordless recipe + { + // CASE 1: email and phoneNumber are not present + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"passwordless\"}]}]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Either email or phoneNumber is required for a passwordless recipe.\"]}]}"); + } + // CASE 2: email and phoneNumber field type is incorrect + try { + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"passwordless\",\"email\":[],\"phoneNumber\":[]}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"email should be of type string for a passwordless recipe.\",\"phoneNumber should be of type string for a passwordless recipe.\"]}]}"); + } + } + // No two loginMethods can have isPrimary as true + { + // CASE 1: email, passwordHash and hashingAlgorithm are not present + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"somehash\",\"hashingAlgorithm\":\"bcrypt\",\"isPrimary\":true},{\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\",\"isPrimary\":true}]}]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"No two loginMethods can have isPrimary as true.\"]}]}"); + } + } + // Can't import than 10000 users at a time + { + try { + JsonObject request = generateUsersJson(10001); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, "{\"error\":\"You can only add 1000 users at a time.\"}"); + } + } + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldReturn200Response() 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 request = generateUsersJson(10000); + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + assertEquals("OK", response.get("status").getAsString()); + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + public static JsonObject generateUsersJson(int numberOfUsers) { + JsonObject userJsonObject = new JsonObject(); + JsonParser parser = new JsonParser(); + + JsonArray usersArray = new JsonArray(); + for (int i = 0; i < numberOfUsers; i++) { + JsonObject user = new JsonObject(); + + user.addProperty("externalUserId", UUID.randomUUID().toString()); + user.add("userMetadata", parser.parse("{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}")); + user.add("roles", parser.parse("[\"role1\", \"role2\"]")); + user.add("totp", parser.parse("[{\"secretKey\":\"secretKey\",\"period\":0,\"skew\":0,\"deviceName\":\"deviceName\"}]")); + + String email = "johndoe+" + i + "@gmail.com"; + + JsonArray loginMethodsArray = new JsonArray(); + loginMethodsArray.add(createEmailLoginMethod(email)); + loginMethodsArray.add(createThirdPartyLoginMethod(email)); + loginMethodsArray.add(createPasswordlessLoginMethod(email)); + user.add("loginMethods", loginMethodsArray); + + usersArray.add(user); + } + + userJsonObject.add("users", usersArray); + return userJsonObject; + } + + private static JsonObject createEmailLoginMethod(String email) { + JsonObject loginMethod = new JsonObject(); + loginMethod.addProperty("tenantId", "public"); + loginMethod.addProperty("email", email); + loginMethod.addProperty("recipeId", "emailpassword"); + loginMethod.addProperty("passwordHash", "$argon2d$v=19$m=12,t=3,p=1$aGI4enNvMmd0Zm0wMDAwMA$r6p7qbr6HD+8CD7sBi4HVw"); + loginMethod.addProperty("hashingAlgorithm", "argon2"); + loginMethod.addProperty("isVerified", true); + loginMethod.addProperty("isPrimary", true); + loginMethod.addProperty("timeJoinedInMSSinceEpoch", 0); + return loginMethod; + } + + private static JsonObject createThirdPartyLoginMethod(String email) { + JsonObject loginMethod = new JsonObject(); + loginMethod.addProperty("tenantId", "somethingelse"); + loginMethod.addProperty("recipeId", "thirdparty"); + loginMethod.addProperty("email", email); + loginMethod.addProperty("thirdPartyId", "google"); + loginMethod.addProperty("thirdPartyUserId", "112618388912586834161"); + loginMethod.addProperty("isVerified", true); + loginMethod.addProperty("isPrimary", false); + loginMethod.addProperty("timeJoinedInMSSinceEpoch", 0); + return loginMethod; + } + + private static JsonObject createPasswordlessLoginMethod(String email) { + JsonObject loginMethod = new JsonObject(); + loginMethod.addProperty("tenantId", "public"); + loginMethod.addProperty("email", email); + loginMethod.addProperty("recipeId", "passwordless"); + loginMethod.addProperty("phoneNumber", "+91-9999999999"); + loginMethod.addProperty("isVerified", true); + loginMethod.addProperty("isPrimary", false); + loginMethod.addProperty("timeJoinedInMSSinceEpoch", 0); + return loginMethod; + } +} From bb7d84e063f51525c37656113f9f8f5c4ae1bb59 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 16 Feb 2024 00:48:15 +0530 Subject: [PATCH 02/23] fix: PR changes --- .../io/supertokens/bulkimport/BulkImport.java | 79 +++++++++ .../BulkImportUserPaginationContainer.java | 32 ++++ .../BulkImportUserPaginationToken.java | 45 +++++ .../io/supertokens/webserver/Webserver.java | 2 + .../api/bulkimport/AddBulkImportUsers.java | 76 +++++---- .../api/bulkimport/GetBulkImportUsers.java | 104 ++++++++++++ .../apis/AddBulkImportUsersTest.java | 55 +++++- .../apis/GetBulkImportUsersTest.java | 158 ++++++++++++++++++ 8 files changed, 520 insertions(+), 31 deletions(-) create mode 100644 src/main/java/io/supertokens/bulkimport/BulkImport.java create mode 100644 src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java create mode 100644 src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java create mode 100644 src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java create mode 100644 src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java new file mode 100644 index 000000000..4b8f55509 --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.utils.Utils; + +import com.google.gson.JsonObject; + +import java.util.ArrayList; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BulkImport { + + public static final int GET_USERS_PAGINATION_LIMIT = 500; + public static final int GET_USERS_DEFAULT_LIMIT = 100; + + public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, ArrayList users) + throws StorageQueryException, TenantOrAppNotFoundException { + while (true) { + try { + appIdentifierWithStorage.getBulkImportStorage().addBulkImportUsers(appIdentifierWithStorage, users); + break; + } catch (io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException ignored) { + // We re-generate the user id for every user and retry + for (BulkImportUser user : users) { + user.id = Utils.getUUID(); + } + } + } + } + + public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorage appIdentifierWithStorage, + @Nonnull Integer limit, @Nullable String status, @Nullable String paginationToken) + throws StorageQueryException, BulkImportUserPaginationToken.InvalidTokenException, + TenantOrAppNotFoundException { + JsonObject[] users; + + if (paginationToken == null) { + users = appIdentifierWithStorage.getBulkImportStorage() + .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, null); + } else { + BulkImportUserPaginationToken tokenInfo = BulkImportUserPaginationToken.extractTokenInfo(paginationToken); + users = appIdentifierWithStorage.getBulkImportStorage() + .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, tokenInfo.bulkImportUserId); + } + + String nextPaginationToken = null; + int maxLoop = users.length; + if (users.length == limit + 1) { + maxLoop = limit; + nextPaginationToken = new BulkImportUserPaginationToken(users[limit].get("id").getAsString()) + .generateToken(); + } + + JsonObject[] resultUsers = new JsonObject[maxLoop]; + System.arraycopy(users, 0, resultUsers, 0, maxLoop); + return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken); + } +} diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java new file mode 100644 index 000000000..a9e4d644c --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.JsonObject; + +public class BulkImportUserPaginationContainer { + public final JsonObject[] users; + public final String nextPaginationToken; + + public BulkImportUserPaginationContainer(@Nonnull JsonObject[] users, @Nullable String nextPaginationToken) { + this.users = users; + this.nextPaginationToken = nextPaginationToken; + } +} \ No newline at end of file diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java new file mode 100644 index 000000000..d66d02041 --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import java.util.Base64; + +public class BulkImportUserPaginationToken { + public final String bulkImportUserId; + + public BulkImportUserPaginationToken(String bulkImportUserId) { + this.bulkImportUserId = bulkImportUserId; + } + + public static BulkImportUserPaginationToken extractTokenInfo(String token) throws InvalidTokenException { + try { + String bulkImportUserId = new String(Base64.getDecoder().decode(token)); + return new BulkImportUserPaginationToken(bulkImportUserId); + } catch (Exception e) { + throw new InvalidTokenException(); + } + } + + public String generateToken() { + return new String(Base64.getEncoder().encode((this.bulkImportUserId).getBytes())); + } + + public static class InvalidTokenException extends Exception { + + private static final long serialVersionUID = 6289026174830695478L; + } +} diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 1664fffe2..6f9c30ef1 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -28,6 +28,7 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.api.accountlinking.*; import io.supertokens.webserver.api.bulkimport.AddBulkImportUsers; +import io.supertokens.webserver.api.bulkimport.GetBulkImportUsers; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -261,6 +262,7 @@ private void setupRoutes() { addAPI(new RequestStatsAPI(main)); addAPI(new AddBulkImportUsers(main)); + addAPI(new GetBulkImportUsers(main)); StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java index 0f98470a1..0e5d3df46 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java @@ -17,13 +17,18 @@ package io.supertokens.webserver.api.bulkimport; import io.supertokens.Main; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlag; import io.supertokens.multitenancy.Multitenancy; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.exceptions.InvalidBulkImportDataException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantConfig; +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; @@ -55,31 +60,27 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S JsonObject input = InputParser.parseJsonObjectOrThrowError(req); JsonArray users = InputParser.parseArrayOrThrowError(input, "users", false); - if (users.size() > MAX_USERS_TO_ADD) { + if (users.size() <= 0 || users.size() > MAX_USERS_TO_ADD) { JsonObject errorResponseJson = new JsonObject(); - errorResponseJson.addProperty("error", "You can only add 1000 users at a time."); - super.sendJsonResponse(400, errorResponseJson, resp); - return; + String errorMsg = users.size() <= 0 ? "You need to add at least one user." + : "You can only add 10000 users at a time."; + errorResponseJson.addProperty("error", errorMsg); + throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); } - AppIdentifier appIdentifier = null; - try { - appIdentifier = getTenantIdentifierFromRequest(req).toAppIdentifier(); - } catch (ServletException e) { - throw new ServletException(e); - } - - TenantConfig[] allTenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main); - ArrayList validTenantIds = new ArrayList<>(); - Arrays.stream(allTenantConfigs) - .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); + AppIdentifier appIdentifier = getTenantIdentifierFromRequest(req).toAppIdentifier(); JsonArray errorsJson = new JsonArray(); ArrayList usersToAdd = new ArrayList<>(); for (int i = 0; i < users.size(); i++) { try { - usersToAdd.add(new BulkImportUser(users.get(i).getAsJsonObject(), validTenantIds, null)); + BulkImportUser user = new BulkImportUser(users.get(i).getAsJsonObject(), null); + usersToAdd.add(user); + + for (BulkImportUser.LoginMethod loginMethod : user.loginMethods) { + validateTenantId(appIdentifier, loginMethod.tenantId, loginMethod.recipeId); + } } catch (InvalidBulkImportDataException e) { JsonObject errorObj = new JsonObject(); @@ -89,12 +90,6 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S errorObj.addProperty("index", i); errorObj.add("errors", errors); - - errorsJson.add(errorObj); - } catch (Exception e) { - JsonObject errorObj = new JsonObject(); - errorObj.addProperty("index", i); - errorObj.addProperty("errors", "An unknown error occurred"); errorsJson.add(errorObj); } } @@ -104,21 +99,46 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S errorResponseJson.addProperty("error", "Data has missing or invalid fields. Please check the users field for more details."); errorResponseJson.add("users", errorsJson); - super.sendJsonResponse(400, errorResponseJson, resp); - return; + throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); } try { AppIdentifierWithStorage appIdentifierWithStorage = getAppIdentifierWithStorage(req); - BulkImportStorage storage = appIdentifierWithStorage.getBulkImportStorage(); - storage.addBulkImportUsers(appIdentifierWithStorage, usersToAdd); - } catch (Exception e) { + BulkImport.addUsers(appIdentifierWithStorage, usersToAdd); + } catch (TenantOrAppNotFoundException | StorageQueryException e) { throw new ServletException(e); } JsonObject result = new JsonObject(); result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); + } + + private void validateTenantId(AppIdentifier appIdentifier, String tenantId, String recipeId) + throws InvalidBulkImportDataException, ServletException { + if (tenantId == null || tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) { + return; + } + + try { + if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures()) + .noneMatch(t -> t == EE_FEATURES.MULTI_TENANCY)) { + throw new InvalidBulkImportDataException(new ArrayList<>( + Arrays.asList("Multitenancy must be enabled before importing users to a different tenant."))); + } + } catch (TenantOrAppNotFoundException | StorageQueryException e) { + throw new ServletException(e); + } + TenantConfig[] allTenantConfigs = Multitenancy + .getAllTenantsForApp(appIdentifier, main); + ArrayList validTenantIds = new ArrayList<>(); + Arrays.stream(allTenantConfigs) + .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); + + if (!validTenantIds.contains(tenantId)) { + throw new InvalidBulkImportDataException( + new ArrayList<>(Arrays.asList("Invalid tenantId: " + tenantId + " for " + recipeId + " recipe."))); + } } } diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java new file mode 100644 index 000000000..f349ac55b --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import java.io.IOException; +import java.util.Arrays; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.supertokens.Main; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.bulkimport.BulkImportUserPaginationContainer; +import io.supertokens.bulkimport.BulkImportUserPaginationToken; +import io.supertokens.output.Logging; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.utils.Utils; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class GetBulkImportUsers extends WebserverAPI { + public GetBulkImportUsers(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/bulk-import/users"; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String status = InputParser.getQueryParamOrThrowError(req, "status", true); + String paginationToken = InputParser.getQueryParamOrThrowError(req, "paginationToken", true); + Integer limit = InputParser.getIntQueryParamOrThrowError(req, "limit", true); + + if (limit != null) { + if (limit > BulkImport.GET_USERS_PAGINATION_LIMIT) { + throw new ServletException( + new BadRequestException("Max limit allowed is " + BulkImport.GET_USERS_PAGINATION_LIMIT)); + } else if (limit < 1) { + throw new ServletException(new BadRequestException("limit must a positive integer with min value 1")); + } + } else { + limit = BulkImport.GET_USERS_DEFAULT_LIMIT; + } + + if (status != null + && !Arrays.asList("NEW", "PROCESSING", "FAILED").contains(status)) { + throw new ServletException(new BadRequestException( + "Invalid value for status. Pass one of NEW, PROCESSING or, FAILED!")); + } + + AppIdentifierWithStorage appIdentifierWithStorage = null; + + try { + appIdentifierWithStorage = this.getAppIdentifierWithStorage(req); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + + try { + BulkImportUserPaginationContainer users = BulkImport.getUsers(appIdentifierWithStorage, limit, status, + paginationToken); + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + + JsonArray usersJson = new JsonArray(); + for (JsonObject user : users.users) { + usersJson.add(user); + } + result.add("users", usersJson); + + if (users.nextPaginationToken != null) { + result.addProperty("nextPaginationToken", users.nextPaginationToken); + } + super.sendJsonResponse(200, result, resp); + } catch (BulkImportUserPaginationToken.InvalidTokenException e) { + Logging.debug(main, null, Utils.exceptionStacktraceToString(e)); + throw new ServletException(new BadRequestException("invalid pagination token")); + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + } +} diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index abc5d1aa0..06fcd7096 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -33,6 +33,8 @@ import com.google.gson.JsonParser; import io.supertokens.ProcessState; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -285,6 +287,40 @@ public void shouldThrow400Error() throws Exception { "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"email should be of type string for a passwordless recipe.\",\"phoneNumber should be of type string for a passwordless recipe.\"]}]}"); } } + // Validate tenantId + { + // CASE 1: Different tenantId when multitenancy is not enabled + try { + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Multitenancy must be enabled before importing users to a different tenant.\"]}]}"); + } + // CASE 2: Different tenantId when multitenancy is enabled + try { + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY}); + + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Invalid tenantId: invalid for passwordless recipe.\"]}]}"); + } + } // No two loginMethods can have isPrimary as true { // CASE 1: email, passwordHash and hashingAlgorithm are not present @@ -301,7 +337,20 @@ public void shouldThrow400Error() throws Exception { "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"No two loginMethods can have isPrimary as true.\"]}]}"); } } - // Can't import than 10000 users at a time + // Can't import less than 1 user at a time + { + try { + JsonObject request = generateUsersJson(0); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, "{\"error\":\"You need to add at least one user.\"}"); + } + } + // Can't import more than 10000 users at a time { try { JsonObject request = generateUsersJson(10001); @@ -311,7 +360,7 @@ public void shouldThrow400Error() throws Exception { } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); - assertEquals(responseString, "{\"error\":\"You can only add 1000 users at a time.\"}"); + assertEquals(responseString, "{\"error\":\"You can only add 10000 users at a time.\"}"); } } @@ -383,7 +432,7 @@ private static JsonObject createEmailLoginMethod(String email) { private static JsonObject createThirdPartyLoginMethod(String email) { JsonObject loginMethod = new JsonObject(); - loginMethod.addProperty("tenantId", "somethingelse"); + loginMethod.addProperty("tenantId", "public"); loginMethod.addProperty("recipeId", "thirdparty"); loginMethod.addProperty("email", email); loginMethod.addProperty("thirdPartyId", "google"); diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java new file mode 100644 index 000000000..7dbba3b04 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2024, 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.bulkimport.apis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +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; + +public class GetBulkImportUsersTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void shouldReturn400Error() 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; + } + + try { + Map params = new HashMap<>(); + params.put("status", "INVALID_STATUS"); + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals( + "Http error. Status Code: 400. Message: Invalid value for status. Pass one of NEW, PROCESSING or, FAILED!", + e.getMessage()); + } + + try { + Map params = new HashMap<>(); + params.put("limit", "0"); + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: limit must a positive integer with min value 1", + e.getMessage()); + } + + try { + Map params = new HashMap<>(); + params.put("limit", "501"); + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Max limit allowed is 500", e.getMessage()); + } + + try { + Map params = new HashMap<>(); + params.put("paginationToken", "invalid_token"); + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: invalid pagination token", e.getMessage()); + } + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldReturn200Response() 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; + } + + // Create a bulk import user to test the GET API + String rawData = "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}"; + { + JsonObject request = new JsonParser().parse(rawData).getAsJsonObject(); + JsonObject res = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + assert res.get("status").getAsString().equals("OK"); + } + + Map params = new HashMap<>(); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + assertEquals("OK", response.get("status").getAsString()); + JsonArray bulkImportUsers = response.get("users").getAsJsonArray(); + assertEquals(1, bulkImportUsers.size()); + JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); + bulkImportUserJson.get("status").getAsString().equals("NEW"); + bulkImportUserJson.get("raw_data").getAsString().equals(rawData); + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From 32625d4ba2edfc865467034b6a7d02e3eb3de295 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Tue, 20 Feb 2024 12:09:30 +0530 Subject: [PATCH 03/23] fix: PR changes --- .../io/supertokens/bulkimport/BulkImport.java | 29 ++- .../BulkImportUserPaginationContainer.java | 8 +- .../BulkImportUserPaginationToken.java | 16 +- .../bulkimport/BulkImportUserUtils.java | 167 ++++++++++++++++++ .../InvalidBulkImportDataException.java | 33 ++++ .../api/bulkimport/AddBulkImportUsers.java | 42 ++++- .../api/bulkimport/GetBulkImportUsers.java | 30 ++-- .../apis/AddBulkImportUsersTest.java | 51 +++++- .../apis/GetBulkImportUsersTest.java | 4 +- 9 files changed, 333 insertions(+), 47 deletions(-) create mode 100644 src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java create mode 100644 src/main/java/io/supertokens/bulkimport/exceptions/InvalidBulkImportDataException.java diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index 4b8f55509..0f95b9cef 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -16,15 +16,16 @@ package io.supertokens.bulkimport; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.Utils; -import com.google.gson.JsonObject; -import java.util.ArrayList; +import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -34,12 +35,12 @@ public class BulkImport { public static final int GET_USERS_PAGINATION_LIMIT = 500; public static final int GET_USERS_DEFAULT_LIMIT = 100; - public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, ArrayList users) + public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, List users) throws StorageQueryException, TenantOrAppNotFoundException { while (true) { try { - appIdentifierWithStorage.getBulkImportStorage().addBulkImportUsers(appIdentifierWithStorage, users); - break; + appIdentifierWithStorage.getBulkImportStorage().addBulkImportUsers(appIdentifierWithStorage, users); + break; } catch (io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException ignored) { // We re-generate the user id for every user and retry for (BulkImportUser user : users) { @@ -50,10 +51,9 @@ public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, A } public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorage appIdentifierWithStorage, - @Nonnull Integer limit, @Nullable String status, @Nullable String paginationToken) - throws StorageQueryException, BulkImportUserPaginationToken.InvalidTokenException, - TenantOrAppNotFoundException { - JsonObject[] users; + @Nonnull Integer limit, @Nullable BulkImportUserStatus status, @Nullable String paginationToken) + throws StorageQueryException, BulkImportUserPaginationToken.InvalidTokenException { + List users; if (paginationToken == null) { users = appIdentifierWithStorage.getBulkImportStorage() @@ -65,15 +65,14 @@ public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorag } String nextPaginationToken = null; - int maxLoop = users.length; - if (users.length == limit + 1) { + int maxLoop = users.size(); + if (users.size() == limit + 1) { maxLoop = limit; - nextPaginationToken = new BulkImportUserPaginationToken(users[limit].get("id").getAsString()) - .generateToken(); + BulkImportUserInfo user = users.get(limit); + nextPaginationToken = new BulkImportUserPaginationToken(user.id, user.createdAt).generateToken(); } - JsonObject[] resultUsers = new JsonObject[maxLoop]; - System.arraycopy(users, 0, resultUsers, 0, maxLoop); + List resultUsers = users.subList(0, maxLoop); return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken); } } diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java index a9e4d644c..2993a83a7 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java @@ -16,16 +16,18 @@ package io.supertokens.bulkimport; +import java.util.List; + import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.google.gson.JsonObject; +import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; public class BulkImportUserPaginationContainer { - public final JsonObject[] users; + public final List users; public final String nextPaginationToken; - public BulkImportUserPaginationContainer(@Nonnull JsonObject[] users, @Nullable String nextPaginationToken) { + public BulkImportUserPaginationContainer(@Nonnull List users, @Nullable String nextPaginationToken) { this.users = users; this.nextPaginationToken = nextPaginationToken; } diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java index d66d02041..0c24211a9 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java @@ -20,22 +20,30 @@ public class BulkImportUserPaginationToken { public final String bulkImportUserId; + public final long createdAt; - public BulkImportUserPaginationToken(String bulkImportUserId) { + public BulkImportUserPaginationToken(String bulkImportUserId, long timeJoined) { this.bulkImportUserId = bulkImportUserId; + this.createdAt = timeJoined; } public static BulkImportUserPaginationToken extractTokenInfo(String token) throws InvalidTokenException { try { - String bulkImportUserId = new String(Base64.getDecoder().decode(token)); - return new BulkImportUserPaginationToken(bulkImportUserId); + String decodedPaginationToken = new String(Base64.getDecoder().decode(token)); + String[] splitDecodedToken = decodedPaginationToken.split(";"); + if (splitDecodedToken.length != 2) { + throw new InvalidTokenException(); + } + String bulkImportUserId = splitDecodedToken[0]; + long timeJoined = Long.parseLong(splitDecodedToken[1]); + return new BulkImportUserPaginationToken(bulkImportUserId, timeJoined); } catch (Exception e) { throw new InvalidTokenException(); } } public String generateToken() { - return new String(Base64.getEncoder().encode((this.bulkImportUserId).getBytes())); + return new String(Base64.getEncoder().encode((this.bulkImportUserId + ";" + this.createdAt).getBytes())); } public static class InvalidTokenException extends Exception { diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java new file mode 100644 index 000000000..43915743a --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.EmailPasswordLoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.ThirdPartyLoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.PasswordlessLoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; +import io.supertokens.pluginInterface.utils.JsonValidatorUtils.ValueType; +import io.supertokens.utils.Utils; + +import static io.supertokens.pluginInterface.utils.JsonValidatorUtils.parseAndValidateField; +import static io.supertokens.pluginInterface.utils.JsonValidatorUtils.validateJsonFieldType; + +public class BulkImportUserUtils { + public static BulkImportUser createBulkImportUserFromJSON(JsonObject userData, String id) throws InvalidBulkImportDataException { + List errors = new ArrayList<>(); + + String externalUserId = parseAndValidateField(userData, "externalUserId", ValueType.STRING, false, String.class, errors, "."); + JsonObject userMetadata = parseAndValidateField(userData, "userMetadata", ValueType.OBJECT, false, JsonObject.class, errors, "."); + List userRoles = getParsedUserRoles(userData, errors); + List totpDevices = getParsedTotpDevices(userData, errors); + List loginMethods = getParsedLoginMethods(userData, errors); + + if (!errors.isEmpty()) { + throw new InvalidBulkImportDataException(errors); + } + return new BulkImportUser(id, externalUserId, userMetadata, userRoles, totpDevices, loginMethods); + } + + private static List getParsedUserRoles(JsonObject userData, List errors) { + JsonArray jsonUserRoles = parseAndValidateField(userData, "roles", ValueType.ARRAY_OF_STRING, + false, + JsonArray.class, errors, "."); + + if (jsonUserRoles == null) { + return null; + } + + List userRoles = new ArrayList<>(); + jsonUserRoles.forEach(role -> userRoles.add(role.getAsString().trim())); + return userRoles; + } + + private static List getParsedTotpDevices(JsonObject userData, List errors) { + JsonArray jsonTotpDevices = parseAndValidateField(userData, "totp", ValueType.ARRAY_OF_OBJECT, false, JsonArray.class, errors, "."); + if (jsonTotpDevices == null) { + return null; + } + + List totpDevices = new ArrayList<>(); + for (JsonElement jsonTotpDeviceEl : jsonTotpDevices) { + JsonObject jsonTotpDevice = jsonTotpDeviceEl.getAsJsonObject(); + + String secretKey = parseAndValidateField(jsonTotpDevice, "secretKey", ValueType.STRING, true, String.class, errors, " for a totp device."); + Number period = parseAndValidateField(jsonTotpDevice, "period", ValueType.NUMBER, true, Number.class, errors, " for a totp device."); + Number skew = parseAndValidateField(jsonTotpDevice, "skew", ValueType.NUMBER, true, Number.class, errors, " for a totp device."); + String deviceName = parseAndValidateField(jsonTotpDevice, "deviceName", ValueType.STRING, false, String.class, errors, " for a totp device."); + + if (period != null && period.intValue() < 1) { + errors.add("period should be > 0 for a totp device."); + } + if (skew != null && skew.intValue() < 0) { + errors.add("skew should be >= 0 for a totp device."); + } + + if(deviceName != null) { + deviceName = deviceName.trim(); + } + totpDevices.add(new TotpDevice(secretKey, period.intValue(), skew.intValue(), deviceName)); + } + return totpDevices; + } + + private static List getParsedLoginMethods(JsonObject userData, List errors) { + JsonArray jsonLoginMethods = parseAndValidateField(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, JsonArray.class, errors, "."); + + if (jsonLoginMethods == null) { + return new ArrayList<>(); + } + + if (jsonLoginMethods.size() == 0) { + errors.add("At least one loginMethod is required."); + return new ArrayList<>(); + } + + Boolean hasPrimaryLoginMethod = false; + + List loginMethods = new ArrayList<>(); + for (JsonElement jsonLoginMethod : jsonLoginMethods) { + JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject(); + + if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) { + if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) { + if (hasPrimaryLoginMethod) { + errors.add("No two loginMethods can have isPrimary as true."); + } + hasPrimaryLoginMethod = true; + } + } + + String recipeId = parseAndValidateField(jsonLoginMethodObj, "recipeId", ValueType.STRING, true, String.class, errors, " for a loginMethod."); + String tenantId = parseAndValidateField(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod."); + Boolean isVerified = parseAndValidateField(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); + Boolean isPrimary = parseAndValidateField(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); + Number timeJoined = parseAndValidateField(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, false, Number.class, errors, " for a loginMethod"); + Long timeJoinedInMSSinceEpoch = timeJoined != null ? timeJoined.longValue() : 0; + + if ("emailpassword".equals(recipeId)) { + String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + String passwordHash = parseAndValidateField(jsonLoginMethodObj, "passwordHash", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + String hashingAlgorithm = parseAndValidateField(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + + email = email != null ? Utils.normaliseEmail(email) : null; + hashingAlgorithm = hashingAlgorithm != null ? hashingAlgorithm.trim().toUpperCase() : null; + + EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, passwordHash, hashingAlgorithm); + loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, emailPasswordLoginMethod, null, null)); + } else if ("thirdparty".equals(recipeId)) { + String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + String thirdPartyId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + String thirdPartyUserId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyUserId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + + email = email != null ? Utils.normaliseEmail(email) : null; + + ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, thirdPartyId, thirdPartyUserId); + loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, thirdPartyLoginMethod, null)); + } else if ("passwordless".equals(recipeId)) { + String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); + String phoneNumber = parseAndValidateField(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); + + email = email != null ? Utils.normaliseEmail(email) : null; + phoneNumber = Utils.normalizeIfPhoneNumber(phoneNumber); + + PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, phoneNumber); + loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, null, passwordlessLoginMethod)); + } else if (recipeId != null) { + errors.add("Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!"); + } + } + return loginMethods; + } +} diff --git a/src/main/java/io/supertokens/bulkimport/exceptions/InvalidBulkImportDataException.java b/src/main/java/io/supertokens/bulkimport/exceptions/InvalidBulkImportDataException.java new file mode 100644 index 000000000..3fbcd8fbd --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/exceptions/InvalidBulkImportDataException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024, 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.bulkimport.exceptions; + +import java.util.List; + +public class InvalidBulkImportDataException extends Exception { + private static final long serialVersionUID = 1L; + public List errors; + + public InvalidBulkImportDataException(List errors) { + super("Data has missing or invalid fields. Please check the errors field for more details."); + this.errors = errors; + } + + public void addError(String error) { + this.errors.add(error); + } +} diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java index 0e5d3df46..524eb4dc8 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java @@ -18,17 +18,24 @@ import io.supertokens.Main; import io.supertokens.bulkimport.BulkImport; +import io.supertokens.bulkimport.BulkImportUserUtils; +import io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException; +import io.supertokens.config.CoreConfig; +import io.supertokens.emailpassword.PasswordHashingUtils; +import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlag; import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; -import io.supertokens.pluginInterface.bulkimport.exceptions.InvalidBulkImportDataException; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.EmailPasswordLoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantConfig; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -68,20 +75,29 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); } - AppIdentifier appIdentifier = getTenantIdentifierFromRequest(req).toAppIdentifier(); + AppIdentifierWithStorage appIdentifierWithStorage; + try { + appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } JsonArray errorsJson = new JsonArray(); ArrayList usersToAdd = new ArrayList<>(); for (int i = 0; i < users.size(); i++) { try { - BulkImportUser user = new BulkImportUser(users.get(i).getAsJsonObject(), null); - usersToAdd.add(user); - + BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(users.get(i).getAsJsonObject(), Utils.getUUID()); + for (BulkImportUser.LoginMethod loginMethod : user.loginMethods) { - validateTenantId(appIdentifier, loginMethod.tenantId, loginMethod.recipeId); + validateTenantId(appIdentifierWithStorage, loginMethod.tenantId, loginMethod.recipeId); + + if (loginMethod.emailPasswordLoginMethod != null) { + validatePasswordHashingAlgorithm(appIdentifierWithStorage, loginMethod.emailPasswordLoginMethod); + } } - } catch (InvalidBulkImportDataException e) { + usersToAdd.add(user); + } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { JsonObject errorObj = new JsonObject(); JsonArray errors = e.errors.stream() @@ -103,7 +119,6 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S } try { - AppIdentifierWithStorage appIdentifierWithStorage = getAppIdentifierWithStorage(req); BulkImport.addUsers(appIdentifierWithStorage, usersToAdd); } catch (TenantOrAppNotFoundException | StorageQueryException e) { throw new ServletException(e); @@ -141,4 +156,15 @@ private void validateTenantId(AppIdentifier appIdentifier, String tenantId, Stri new ArrayList<>(Arrays.asList("Invalid tenantId: " + tenantId + " for " + recipeId + " recipe."))); } } + + private void validatePasswordHashingAlgorithm(AppIdentifier appIdentifier, EmailPasswordLoginMethod emailPasswordLoginMethod) throws InvalidBulkImportDataException, ServletException { + try { + CoreConfig.PASSWORD_HASHING_ALG passwordHashingAlgorithm = CoreConfig.PASSWORD_HASHING_ALG.valueOf(emailPasswordLoginMethod.hashingAlgorithm); + PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, emailPasswordLoginMethod.passwordHash, passwordHashingAlgorithm); + } catch (UnsupportedPasswordHashingFormatException | TenantOrAppNotFoundException e) { + throw new InvalidBulkImportDataException(new ArrayList<>(Arrays.asList(e.getMessage()))); + } catch (IllegalArgumentException e) { + throw new InvalidBulkImportDataException(new ArrayList<>(Arrays.asList("Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"))); + } + } } diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java index f349ac55b..f89fe39c3 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java @@ -17,7 +17,6 @@ package io.supertokens.webserver.api.bulkimport; import java.io.IOException; -import java.util.Arrays; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -26,7 +25,10 @@ import io.supertokens.bulkimport.BulkImport; import io.supertokens.bulkimport.BulkImportUserPaginationContainer; import io.supertokens.bulkimport.BulkImportUserPaginationToken; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; +import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -49,7 +51,7 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String status = InputParser.getQueryParamOrThrowError(req, "status", true); + String statusString = InputParser.getQueryParamOrThrowError(req, "status", true); String paginationToken = InputParser.getQueryParamOrThrowError(req, "paginationToken", true); Integer limit = InputParser.getIntQueryParamOrThrowError(req, "limit", true); @@ -64,29 +66,31 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se limit = BulkImport.GET_USERS_DEFAULT_LIMIT; } - if (status != null - && !Arrays.asList("NEW", "PROCESSING", "FAILED").contains(status)) { - throw new ServletException(new BadRequestException( - "Invalid value for status. Pass one of NEW, PROCESSING or, FAILED!")); + BulkImportUserStatus status = null; + if (statusString != null) { + try { + status = BulkImportUserStatus.valueOf(statusString); + } catch (IllegalArgumentException e) { + throw new ServletException(new BadRequestException("Invalid value for status. Pass one of NEW, PROCESSING, or FAILED!")); + } } AppIdentifierWithStorage appIdentifierWithStorage = null; try { - appIdentifierWithStorage = this.getAppIdentifierWithStorage(req); - } catch (TenantOrAppNotFoundException e) { + appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } try { - BulkImportUserPaginationContainer users = BulkImport.getUsers(appIdentifierWithStorage, limit, status, - paginationToken); + BulkImportUserPaginationContainer users = BulkImport.getUsers(appIdentifierWithStorage, limit, status, paginationToken); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); JsonArray usersJson = new JsonArray(); - for (JsonObject user : users.users) { - usersJson.add(user); + for (BulkImportUserInfo user : users.users) { + usersJson.add(user.toJsonObject()); } result.add("users", usersJson); @@ -97,7 +101,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se } catch (BulkImportUserPaginationToken.InvalidTokenException e) { Logging.debug(main, null, Utils.exceptionStacktraceToString(e)); throw new ServletException(new BadRequestException("invalid pagination token")); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException e) { throw new ServletException(e); } } diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index 06fcd7096..f96d5b485 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.util.HashMap; import java.util.UUID; import org.junit.AfterClass; @@ -382,9 +383,55 @@ public void shouldReturn200Response() throws Exception { JsonObject request = generateUsersJson(10000); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/add-users", + request, 1000, 10000, null, Utils.getCdiVersionStringLatestForTests(), null); + assertEquals("OK", response.get("status").getAsString()); + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldNormaliseFields() 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 request = generateUsersJson(1); + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); assertEquals("OK", response.get("status").getAsString()); + JsonObject getResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + new HashMap<>(), 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + assertEquals("OK", getResponse.get("status").getAsString()); + JsonArray bulkImportUsers = getResponse.get("users").getAsJsonArray(); + assertEquals(1, bulkImportUsers.size()); + + JsonParser parser = new JsonParser(); + JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); + JsonArray loginMethods = parser.parse(bulkImportUserJson.get("rawData").getAsString()).getAsJsonObject().getAsJsonArray("loginMethods"); + + for (int i = 0; i < loginMethods.size(); i++) { + JsonObject loginMethod = loginMethods.get(i).getAsJsonObject(); + if (loginMethod.has("email")) { + assertEquals("johndoe+1@gmail.com", loginMethod.get("hashingAlgorithm").getAsString()); + } + if (loginMethod.has("phoneNumber")) { + assertEquals("919999999999", loginMethod.get("phoneNumber").getAsString()); + } + if (loginMethod.has("hashingAlgorithm")) { + assertEquals("ARGON2", loginMethod.get("hashingAlgorithm").getAsString()); + } + } + process.kill(); Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } @@ -400,9 +447,9 @@ public static JsonObject generateUsersJson(int numberOfUsers) { user.addProperty("externalUserId", UUID.randomUUID().toString()); user.add("userMetadata", parser.parse("{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}")); user.add("roles", parser.parse("[\"role1\", \"role2\"]")); - user.add("totp", parser.parse("[{\"secretKey\":\"secretKey\",\"period\":0,\"skew\":0,\"deviceName\":\"deviceName\"}]")); + user.add("totp", parser.parse("[{\"secretKey\":\"secretKey\",\"period\": 30,\"skew\":1,\"deviceName\":\"deviceName\"}]")); - String email = "johndoe+" + i + "@gmail.com"; + String email = " johndoe+" + i + "@gmail.com "; JsonArray loginMethodsArray = new JsonArray(); loginMethodsArray.add(createEmailLoginMethod(email)); diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java index 7dbba3b04..64539bee8 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java @@ -75,7 +75,7 @@ public void shouldReturn400Error() throws Exception { } catch (io.supertokens.test.httpRequest.HttpResponseException e) { assertEquals(400, e.statusCode); assertEquals( - "Http error. Status Code: 400. Message: Invalid value for status. Pass one of NEW, PROCESSING or, FAILED!", + "Http error. Status Code: 400. Message: Invalid value for status. Pass one of NEW, PROCESSING, or FAILED!", e.getMessage()); } @@ -150,7 +150,7 @@ public void shouldReturn200Response() throws Exception { assertEquals(1, bulkImportUsers.size()); JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); bulkImportUserJson.get("status").getAsString().equals("NEW"); - bulkImportUserJson.get("raw_data").getAsString().equals(rawData); + bulkImportUserJson.get("rawData").getAsString().equals(rawData); process.kill(); Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); From d5f0c677de93bc0cff42154fb8c40f0e89b87b12 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Tue, 20 Feb 2024 17:50:49 +0530 Subject: [PATCH 04/23] fix: PR changes --- .../io/supertokens/bulkimport/BulkImport.java | 4 +- .../BulkImportUserPaginationToken.java | 8 +- .../bulkimport/BulkImportUserUtils.java | 215 +++++++++++------- .../supertokens/utils/JsonValidatorUtils.java | 117 ++++++++++ .../api/bulkimport/AddBulkImportUsers.java | 63 +---- .../apis/AddBulkImportUsersTest.java | 4 +- 6 files changed, 265 insertions(+), 146 deletions(-) create mode 100644 src/main/java/io/supertokens/utils/JsonValidatorUtils.java diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index 0f95b9cef..234c13c3d 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -57,11 +57,11 @@ public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorag if (paginationToken == null) { users = appIdentifierWithStorage.getBulkImportStorage() - .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, null); + .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, null, null); } else { BulkImportUserPaginationToken tokenInfo = BulkImportUserPaginationToken.extractTokenInfo(paginationToken); users = appIdentifierWithStorage.getBulkImportStorage() - .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, tokenInfo.bulkImportUserId); + .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, tokenInfo.bulkImportUserId, tokenInfo.createdAt); } String nextPaginationToken = null; diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java index 0c24211a9..8a492c2ca 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java @@ -22,9 +22,9 @@ public class BulkImportUserPaginationToken { public final String bulkImportUserId; public final long createdAt; - public BulkImportUserPaginationToken(String bulkImportUserId, long timeJoined) { + public BulkImportUserPaginationToken(String bulkImportUserId, long createdAt) { this.bulkImportUserId = bulkImportUserId; - this.createdAt = timeJoined; + this.createdAt = createdAt; } public static BulkImportUserPaginationToken extractTokenInfo(String token) throws InvalidTokenException { @@ -35,8 +35,8 @@ public static BulkImportUserPaginationToken extractTokenInfo(String token) throw throw new InvalidTokenException(); } String bulkImportUserId = splitDecodedToken[0]; - long timeJoined = Long.parseLong(splitDecodedToken[1]); - return new BulkImportUserPaginationToken(bulkImportUserId, timeJoined); + long createdAt = Long.parseLong(splitDecodedToken[1]); + return new BulkImportUserPaginationToken(bulkImportUserId, createdAt); } catch (Exception e) { throw new InvalidTokenException(); } diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index 43915743a..dc98dcef1 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -17,40 +17,56 @@ package io.supertokens.bulkimport; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import io.supertokens.Main; import io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException; +import io.supertokens.config.CoreConfig; +import io.supertokens.emailpassword.PasswordHashingUtils; +import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlag; +import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.EmailPasswordLoginMethod; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.ThirdPartyLoginMethod; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.PasswordlessLoginMethod; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; -import io.supertokens.pluginInterface.utils.JsonValidatorUtils.ValueType; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantConfig; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.Utils; +import io.supertokens.utils.JsonValidatorUtils.ValueType; -import static io.supertokens.pluginInterface.utils.JsonValidatorUtils.parseAndValidateField; -import static io.supertokens.pluginInterface.utils.JsonValidatorUtils.validateJsonFieldType; +import static io.supertokens.utils.JsonValidatorUtils.parseAndValidateField; +import static io.supertokens.utils.JsonValidatorUtils.validateJsonFieldType; public class BulkImportUserUtils { - public static BulkImportUser createBulkImportUserFromJSON(JsonObject userData, String id) throws InvalidBulkImportDataException { + public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifier appIdentifier, JsonObject userData, String id) + throws InvalidBulkImportDataException, StorageQueryException, TenantOrAppNotFoundException { List errors = new ArrayList<>(); - String externalUserId = parseAndValidateField(userData, "externalUserId", ValueType.STRING, false, String.class, errors, "."); - JsonObject userMetadata = parseAndValidateField(userData, "userMetadata", ValueType.OBJECT, false, JsonObject.class, errors, "."); - List userRoles = getParsedUserRoles(userData, errors); + String externalUserId = parseAndValidateField(userData, "externalUserId", ValueType.STRING, false, String.class, + errors, "."); + JsonObject userMetadata = parseAndValidateField(userData, "userMetadata", ValueType.OBJECT, false, + JsonObject.class, errors, "."); + List userRoles = getParsedUserRoles(userData, errors); List totpDevices = getParsedTotpDevices(userData, errors); - List loginMethods = getParsedLoginMethods(userData, errors); + List loginMethods = getParsedLoginMethods(main, appIdentifier, userData, errors); if (!errors.isEmpty()) { throw new InvalidBulkImportDataException(errors); } return new BulkImportUser(id, externalUserId, userMetadata, userRoles, totpDevices, loginMethods); - } + } private static List getParsedUserRoles(JsonObject userData, List errors) { JsonArray jsonUserRoles = parseAndValidateField(userData, "roles", ValueType.ARRAY_OF_STRING, @@ -88,7 +104,7 @@ private static List getParsedTotpDevices(JsonObject userData, List= 0 for a totp device."); } - if(deviceName != null) { + if (deviceName != null) { deviceName = deviceName.trim(); } totpDevices.add(new TotpDevice(secretKey, period.intValue(), skew.intValue(), deviceName)); @@ -96,72 +112,115 @@ private static List getParsedTotpDevices(JsonObject userData, List getParsedLoginMethods(JsonObject userData, List errors) { - JsonArray jsonLoginMethods = parseAndValidateField(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, JsonArray.class, errors, "."); - - if (jsonLoginMethods == null) { - return new ArrayList<>(); - } - - if (jsonLoginMethods.size() == 0) { - errors.add("At least one loginMethod is required."); - return new ArrayList<>(); - } - - Boolean hasPrimaryLoginMethod = false; - - List loginMethods = new ArrayList<>(); - for (JsonElement jsonLoginMethod : jsonLoginMethods) { - JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject(); - - if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) { - if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) { - if (hasPrimaryLoginMethod) { - errors.add("No two loginMethods can have isPrimary as true."); - } - hasPrimaryLoginMethod = true; - } - } - - String recipeId = parseAndValidateField(jsonLoginMethodObj, "recipeId", ValueType.STRING, true, String.class, errors, " for a loginMethod."); - String tenantId = parseAndValidateField(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod."); - Boolean isVerified = parseAndValidateField(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); - Boolean isPrimary = parseAndValidateField(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); - Number timeJoined = parseAndValidateField(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, false, Number.class, errors, " for a loginMethod"); - Long timeJoinedInMSSinceEpoch = timeJoined != null ? timeJoined.longValue() : 0; - - if ("emailpassword".equals(recipeId)) { - String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); - String passwordHash = parseAndValidateField(jsonLoginMethodObj, "passwordHash", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); - String hashingAlgorithm = parseAndValidateField(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); - - email = email != null ? Utils.normaliseEmail(email) : null; - hashingAlgorithm = hashingAlgorithm != null ? hashingAlgorithm.trim().toUpperCase() : null; - - EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, passwordHash, hashingAlgorithm); - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, emailPasswordLoginMethod, null, null)); - } else if ("thirdparty".equals(recipeId)) { - String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); - String thirdPartyId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); - String thirdPartyUserId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyUserId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); - - email = email != null ? Utils.normaliseEmail(email) : null; - - ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, thirdPartyId, thirdPartyUserId); - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, thirdPartyLoginMethod, null)); - } else if ("passwordless".equals(recipeId)) { - String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); - String phoneNumber = parseAndValidateField(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); - - email = email != null ? Utils.normaliseEmail(email) : null; - phoneNumber = Utils.normalizeIfPhoneNumber(phoneNumber); - - PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, phoneNumber); - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, null, passwordlessLoginMethod)); - } else if (recipeId != null) { - errors.add("Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!"); - } - } - return loginMethods; - } + private static List getParsedLoginMethods(Main main, AppIdentifier appIdentifier, JsonObject userData, List errors) + throws StorageQueryException, TenantOrAppNotFoundException { + JsonArray jsonLoginMethods = parseAndValidateField(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, JsonArray.class, errors, "."); + + if (jsonLoginMethods == null) { + return new ArrayList<>(); + } + + if (jsonLoginMethods.size() == 0) { + errors.add("At least one loginMethod is required."); + return new ArrayList<>(); + } + + boolean hasPrimaryLoginMethod = false; + + List loginMethods = new ArrayList<>(); + for (JsonElement jsonLoginMethod : jsonLoginMethods) { + JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject(); + + if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) { + if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) { + if (hasPrimaryLoginMethod) { + errors.add("No two loginMethods can have isPrimary as true."); + } + hasPrimaryLoginMethod = true; + } + } + + String recipeId = parseAndValidateField(jsonLoginMethodObj, "recipeId", ValueType.STRING, true, String.class, errors, " for a loginMethod."); + String tenantId = parseAndValidateField(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod."); + Boolean isVerified = parseAndValidateField(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); + Boolean isPrimary = parseAndValidateField(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); + Number timeJoined = parseAndValidateField(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, false, Number.class, errors, " for a loginMethod"); + long timeJoinedInMSSinceEpoch = timeJoined != null ? timeJoined.longValue() : 0; + + validateTenantId(main, appIdentifier, tenantId, recipeId, errors); + + if ("emailpassword".equals(recipeId)) { + String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + String passwordHash = parseAndValidateField(jsonLoginMethodObj, "passwordHash", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + String hashingAlgorithm = parseAndValidateField(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + + email = email != null ? Utils.normaliseEmail(email) : null; + hashingAlgorithm = hashingAlgorithm != null ? hashingAlgorithm.trim().toUpperCase() : null; + + EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, passwordHash, hashingAlgorithm); + + validatePasswordHashingAlgorithm(main, appIdentifier, emailPasswordLoginMethod, errors); + + loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, emailPasswordLoginMethod, null, null)); + } else if ("thirdparty".equals(recipeId)) { + String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + String thirdPartyId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + String thirdPartyUserId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyUserId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + + email = email != null ? Utils.normaliseEmail(email) : null; + + ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, thirdPartyId, thirdPartyUserId); + loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, thirdPartyLoginMethod, null)); + } else if ("passwordless".equals(recipeId)) { + String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); + String phoneNumber = parseAndValidateField(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); + + email = email != null ? Utils.normaliseEmail(email) : null; + phoneNumber = Utils.normalizeIfPhoneNumber(phoneNumber); + + PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, phoneNumber); + loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, null, passwordlessLoginMethod)); + } else if (recipeId != null) { + errors.add("Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!"); + } + } + return loginMethods; + } + + private static void validateTenantId(Main main, AppIdentifier appIdentifier, String tenantId, String recipeId, List errors) + throws StorageQueryException, TenantOrAppNotFoundException { + if (tenantId == null || tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) { + return; + } + + if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures()) + .noneMatch(t -> t == EE_FEATURES.MULTI_TENANCY)) { + errors.add("Multitenancy must be enabled before importing users to a different tenant."); + return; + } + + TenantConfig[] allTenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main); + ArrayList validTenantIds = new ArrayList<>(); + Arrays.stream(allTenantConfigs) + .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); + + if (!validTenantIds.contains(tenantId)) { + errors.add("Invalid tenantId: " + tenantId + " for " + recipeId + " recipe."); + } + } + + private static void validatePasswordHashingAlgorithm(Main main, AppIdentifier appIdentifier, EmailPasswordLoginMethod emailPasswordLoginMethod, List errors) throws TenantOrAppNotFoundException { + if(emailPasswordLoginMethod.hashingAlgorithm == null || emailPasswordLoginMethod.passwordHash == null) { + return; + } + + try { + CoreConfig.PASSWORD_HASHING_ALG passwordHashingAlgorithm = CoreConfig.PASSWORD_HASHING_ALG.valueOf(emailPasswordLoginMethod.hashingAlgorithm); + PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, emailPasswordLoginMethod.passwordHash, passwordHashingAlgorithm); + } catch (UnsupportedPasswordHashingFormatException e) { + errors.add(e.getMessage()); + } catch (IllegalArgumentException e) { + errors.add("Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"); + } + } } diff --git a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java new file mode 100644 index 000000000..e8ee6d569 --- /dev/null +++ b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024, 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.utils; + + import java.util.ArrayList; + import java.util.List; + + import com.google.gson.JsonArray; + import com.google.gson.JsonElement; + import com.google.gson.JsonObject; + + public class JsonValidatorUtils { + @SuppressWarnings("unchecked") + public static T parseAndValidateField(JsonObject jsonObject, String key, ValueType expectedType, + boolean isRequired, Class targetType, List errors, String errorSuffix) { + if (jsonObject.has(key)) { + if (validateJsonFieldType(jsonObject, key, expectedType)) { + T value; + switch (expectedType) { + case STRING: + value = (T) jsonObject.get(key).getAsString(); + break; + case NUMBER: + value = (T) jsonObject.get(key).getAsNumber(); + break; + case BOOLEAN: + Boolean boolValue = jsonObject.get(key).getAsBoolean(); + value = (T) boolValue; + break; + case OBJECT: + value = (T) jsonObject.get(key).getAsJsonObject(); + break; + case ARRAY_OF_OBJECT, ARRAY_OF_STRING: + value = (T) jsonObject.get(key).getAsJsonArray(); + break; + default: + value = null; + break; + } + if (value != null) { + return targetType.cast(value); + } else { + errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); + } + } else { + errors.add(key + " should be of type " + getTypeForErrorMessage(expectedType) + errorSuffix); + } + } else if (isRequired) { + errors.add(key + " is required" + errorSuffix); + } + return null; + } + + public enum ValueType { + STRING, + NUMBER, + BOOLEAN, + OBJECT, + ARRAY_OF_STRING, + ARRAY_OF_OBJECT + } + + private static String getTypeForErrorMessage(ValueType type) { + return switch (type) { + case STRING -> "string"; + case NUMBER -> "number"; + case BOOLEAN -> "boolean"; + case OBJECT -> "object"; + case ARRAY_OF_STRING -> "array of string"; + case ARRAY_OF_OBJECT -> "array of object"; + }; + } + + public static boolean validateJsonFieldType(JsonObject jsonObject, String key, ValueType expectedType) { + if (jsonObject.has(key)) { + return switch (expectedType) { + case STRING -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isString() + && !jsonObject.get(key).getAsString().isEmpty(); + case NUMBER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); + case BOOLEAN -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isBoolean(); + case OBJECT -> jsonObject.get(key).isJsonObject(); + case ARRAY_OF_OBJECT, ARRAY_OF_STRING -> jsonObject.get(key).isJsonArray() + && validateArrayElements(jsonObject.getAsJsonArray(key), expectedType); + default -> false; + }; + } + return false; + } + + public static boolean validateArrayElements(JsonArray array, ValueType expectedType) { + List elements = new ArrayList<>(); + array.forEach(elements::add); + + return switch (expectedType) { + case ARRAY_OF_OBJECT -> elements.stream().allMatch(JsonElement::isJsonObject); + case ARRAY_OF_STRING -> + elements.stream().allMatch(el -> el.isJsonPrimitive() && el.getAsJsonPrimitive().isString() + && !el.getAsString().isEmpty()); + default -> false; + }; + } + } + \ No newline at end of file diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java index 524eb4dc8..8239cd0ac 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java @@ -19,21 +19,10 @@ import io.supertokens.Main; import io.supertokens.bulkimport.BulkImport; import io.supertokens.bulkimport.BulkImportUserUtils; -import io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException; -import io.supertokens.config.CoreConfig; -import io.supertokens.emailpassword.PasswordHashingUtils; -import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException; -import io.supertokens.featureflag.EE_FEATURES; -import io.supertokens.featureflag.FeatureFlag; -import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; -import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.EmailPasswordLoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.TenantConfig; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; @@ -44,7 +33,6 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -87,15 +75,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S for (int i = 0; i < users.size(); i++) { try { - BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(users.get(i).getAsJsonObject(), Utils.getUUID()); - - for (BulkImportUser.LoginMethod loginMethod : user.loginMethods) { - validateTenantId(appIdentifierWithStorage, loginMethod.tenantId, loginMethod.recipeId); - - if (loginMethod.emailPasswordLoginMethod != null) { - validatePasswordHashingAlgorithm(appIdentifierWithStorage, loginMethod.emailPasswordLoginMethod); - } - } + BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifierWithStorage, users.get(i).getAsJsonObject(), Utils.getUUID()); usersToAdd.add(user); } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { JsonObject errorObj = new JsonObject(); @@ -107,6 +87,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S errorObj.addProperty("index", i); errorObj.add("errors", errors); errorsJson.add(errorObj); + } catch (TenantOrAppNotFoundException | StorageQueryException e) { + throw new ServletException(e); } } @@ -128,43 +110,4 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); } - - private void validateTenantId(AppIdentifier appIdentifier, String tenantId, String recipeId) - throws InvalidBulkImportDataException, ServletException { - if (tenantId == null || tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) { - return; - } - - try { - if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures()) - .noneMatch(t -> t == EE_FEATURES.MULTI_TENANCY)) { - throw new InvalidBulkImportDataException(new ArrayList<>( - Arrays.asList("Multitenancy must be enabled before importing users to a different tenant."))); - } - } catch (TenantOrAppNotFoundException | StorageQueryException e) { - throw new ServletException(e); - } - - TenantConfig[] allTenantConfigs = Multitenancy - .getAllTenantsForApp(appIdentifier, main); - ArrayList validTenantIds = new ArrayList<>(); - Arrays.stream(allTenantConfigs) - .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); - - if (!validTenantIds.contains(tenantId)) { - throw new InvalidBulkImportDataException( - new ArrayList<>(Arrays.asList("Invalid tenantId: " + tenantId + " for " + recipeId + " recipe."))); - } - } - - private void validatePasswordHashingAlgorithm(AppIdentifier appIdentifier, EmailPasswordLoginMethod emailPasswordLoginMethod) throws InvalidBulkImportDataException, ServletException { - try { - CoreConfig.PASSWORD_HASHING_ALG passwordHashingAlgorithm = CoreConfig.PASSWORD_HASHING_ALG.valueOf(emailPasswordLoginMethod.hashingAlgorithm); - PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, emailPasswordLoginMethod.passwordHash, passwordHashingAlgorithm); - } catch (UnsupportedPasswordHashingFormatException | TenantOrAppNotFoundException e) { - throw new InvalidBulkImportDataException(new ArrayList<>(Arrays.asList(e.getMessage()))); - } catch (IllegalArgumentException e) { - throw new InvalidBulkImportDataException(new ArrayList<>(Arrays.asList("Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"))); - } - } } diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index f96d5b485..f20bceb42 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -216,7 +216,7 @@ public void shouldThrow400Error() throws Exception { // CASE 3: hashingAlgorithm is not one of bcrypt, argon2, firebase_scrypt try { JsonObject request = new JsonParser().parse( - "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"somehash\",\"hashingAlgorithm\":\"invalid_algorithm\"}]}]}") + "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"$2a\",\"hashingAlgorithm\":\"invalid_algorithm\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/add-users", @@ -327,7 +327,7 @@ public void shouldThrow400Error() throws Exception { // CASE 1: email, passwordHash and hashingAlgorithm are not present try { JsonObject request = new JsonParser() - .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"somehash\",\"hashingAlgorithm\":\"bcrypt\",\"isPrimary\":true},{\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\",\"isPrimary\":true}]}]}").getAsJsonObject(); + .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"$2a\",\"hashingAlgorithm\":\"bcrypt\",\"isPrimary\":true},{\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\",\"isPrimary\":true}]}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/add-users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); From d7ba8d1da71ec1f8dcb54490fc8331af76524b43 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Thu, 22 Feb 2024 12:04:16 +0530 Subject: [PATCH 05/23] fix: PR changes --- .../bulkimport/BulkImportUserUtils.java | 230 ++++++++++++------ .../supertokens/utils/JsonValidatorUtils.java | 2 +- 2 files changed, 163 insertions(+), 69 deletions(-) diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index dc98dcef1..d9b2c1f7c 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -46,7 +46,7 @@ import io.supertokens.utils.Utils; import io.supertokens.utils.JsonValidatorUtils.ValueType; -import static io.supertokens.utils.JsonValidatorUtils.parseAndValidateField; +import static io.supertokens.utils.JsonValidatorUtils.parseAndValidateFieldType; import static io.supertokens.utils.JsonValidatorUtils.validateJsonFieldType; public class BulkImportUserUtils { @@ -54,9 +54,9 @@ public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifi throws InvalidBulkImportDataException, StorageQueryException, TenantOrAppNotFoundException { List errors = new ArrayList<>(); - String externalUserId = parseAndValidateField(userData, "externalUserId", ValueType.STRING, false, String.class, + String externalUserId = parseAndValidateFieldType(userData, "externalUserId", ValueType.STRING, false, String.class, errors, "."); - JsonObject userMetadata = parseAndValidateField(userData, "userMetadata", ValueType.OBJECT, false, + JsonObject userMetadata = parseAndValidateFieldType(userData, "userMetadata", ValueType.OBJECT, false, JsonObject.class, errors, "."); List userRoles = getParsedUserRoles(userData, errors); List totpDevices = getParsedTotpDevices(userData, errors); @@ -69,7 +69,7 @@ public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifi } private static List getParsedUserRoles(JsonObject userData, List errors) { - JsonArray jsonUserRoles = parseAndValidateField(userData, "roles", ValueType.ARRAY_OF_STRING, + JsonArray jsonUserRoles = parseAndValidateFieldType(userData, "roles", ValueType.ARRAY_OF_STRING, false, JsonArray.class, errors, "."); @@ -77,13 +77,14 @@ private static List getParsedUserRoles(JsonObject userData, List return null; } + // We already know that the jsonUserRoles is an array of non-empty strings, we will normalise each role now List userRoles = new ArrayList<>(); - jsonUserRoles.forEach(role -> userRoles.add(role.getAsString().trim())); + jsonUserRoles.forEach(role -> userRoles.add(validateAndNormaliseUserRole(role.getAsString()))); return userRoles; } private static List getParsedTotpDevices(JsonObject userData, List errors) { - JsonArray jsonTotpDevices = parseAndValidateField(userData, "totp", ValueType.ARRAY_OF_OBJECT, false, JsonArray.class, errors, "."); + JsonArray jsonTotpDevices = parseAndValidateFieldType(userData, "totp", ValueType.ARRAY_OF_OBJECT, false, JsonArray.class, errors, "."); if (jsonTotpDevices == null) { return null; } @@ -92,29 +93,26 @@ private static List getParsedTotpDevices(JsonObject userData, List 0 for a totp device."); - } - if (skew != null && skew.intValue() < 0) { - errors.add("skew should be >= 0 for a totp device."); - } - - if (deviceName != null) { - deviceName = deviceName.trim(); - } - totpDevices.add(new TotpDevice(secretKey, period.intValue(), skew.intValue(), deviceName)); + String secretKey = parseAndValidateFieldType(jsonTotpDevice, "secretKey", ValueType.STRING, true, String.class, errors, " for a totp device."); + Number period = parseAndValidateFieldType(jsonTotpDevice, "period", ValueType.NUMBER, true, Number.class, errors, " for a totp device."); + Number skew = parseAndValidateFieldType(jsonTotpDevice, "skew", ValueType.NUMBER, true, Number.class, errors, " for a totp device."); + String deviceName = parseAndValidateFieldType(jsonTotpDevice, "deviceName", ValueType.STRING, false, String.class, errors, " for a totp device."); + + totpDevices.add( + new TotpDevice( + validateAndNormaliseTotpSecretKey(secretKey), + validateAndNormaliseTotpPeriod(period, errors), + validateAndNormaliseTotpSkew(skew, errors), + validateAndNormaliseTotpDeviceName(deviceName) + ) + ); } return totpDevices; } private static List getParsedLoginMethods(Main main, AppIdentifier appIdentifier, JsonObject userData, List errors) throws StorageQueryException, TenantOrAppNotFoundException { - JsonArray jsonLoginMethods = parseAndValidateField(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, JsonArray.class, errors, "."); + JsonArray jsonLoginMethods = parseAndValidateFieldType(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, JsonArray.class, errors, "."); if (jsonLoginMethods == null) { return new ArrayList<>(); @@ -125,102 +123,198 @@ private static List getParsedLoginMethods(Main main, AppIdentifier return new ArrayList<>(); } - boolean hasPrimaryLoginMethod = false; + validateAndNormaliseIsPrimaryField(jsonLoginMethods, errors); List loginMethods = new ArrayList<>(); + for (JsonElement jsonLoginMethod : jsonLoginMethods) { JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject(); - if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) { - if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) { - if (hasPrimaryLoginMethod) { - errors.add("No two loginMethods can have isPrimary as true."); - } - hasPrimaryLoginMethod = true; - } - } + String recipeId = parseAndValidateFieldType(jsonLoginMethodObj, "recipeId", ValueType.STRING, true, String.class, errors, " for a loginMethod."); + String tenantId = parseAndValidateFieldType(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod."); + Boolean isVerified = parseAndValidateFieldType(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); + Boolean isPrimary = parseAndValidateFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); + Number timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, false, Number.class, errors, " for a loginMethod"); - String recipeId = parseAndValidateField(jsonLoginMethodObj, "recipeId", ValueType.STRING, true, String.class, errors, " for a loginMethod."); - String tenantId = parseAndValidateField(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod."); - Boolean isVerified = parseAndValidateField(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); - Boolean isPrimary = parseAndValidateField(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); - Number timeJoined = parseAndValidateField(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, false, Number.class, errors, " for a loginMethod"); - long timeJoinedInMSSinceEpoch = timeJoined != null ? timeJoined.longValue() : 0; - - validateTenantId(main, appIdentifier, tenantId, recipeId, errors); + recipeId = validateAndNormaliseRecipeId(recipeId, errors); + tenantId= validateAndNormaliseTenantId(main, appIdentifier, tenantId, recipeId, errors); + isVerified = validateAndNormaliseIsVerified(isVerified); + long timeJoinedInMSSinceEpoch = validateAndNormaliseTimeJoined(timeJoined); if ("emailpassword".equals(recipeId)) { - String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); - String passwordHash = parseAndValidateField(jsonLoginMethodObj, "passwordHash", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); - String hashingAlgorithm = parseAndValidateField(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + String passwordHash = parseAndValidateFieldType(jsonLoginMethodObj, "passwordHash", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + String hashingAlgorithm = parseAndValidateFieldType(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); - email = email != null ? Utils.normaliseEmail(email) : null; - hashingAlgorithm = hashingAlgorithm != null ? hashingAlgorithm.trim().toUpperCase() : null; + email = validateAndNormaliseEmail(email); + passwordHash = validateAndNormalisePasswordHash(passwordHash); + hashingAlgorithm = validateAndNormaliseHashingAlgorithm(main, appIdentifier, hashingAlgorithm, passwordHash, errors); EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, passwordHash, hashingAlgorithm); - - validatePasswordHashingAlgorithm(main, appIdentifier, emailPasswordLoginMethod, errors); - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, emailPasswordLoginMethod, null, null)); } else if ("thirdparty".equals(recipeId)) { - String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); - String thirdPartyId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); - String thirdPartyUserId = parseAndValidateField(jsonLoginMethodObj, "thirdPartyUserId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + String thirdPartyId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + String thirdPartyUserId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyUserId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); - email = email != null ? Utils.normaliseEmail(email) : null; + email = validateAndNormaliseEmail(email); + thirdPartyId = validateAndNormaliseThirdPartyId(thirdPartyId); + thirdPartyUserId = validateAndNormaliseThirdPartyUserId(thirdPartyUserId); ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, thirdPartyId, thirdPartyUserId); loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, thirdPartyLoginMethod, null)); } else if ("passwordless".equals(recipeId)) { - String email = parseAndValidateField(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); - String phoneNumber = parseAndValidateField(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); + String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); + String phoneNumber = parseAndValidateFieldType(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); - email = email != null ? Utils.normaliseEmail(email) : null; - phoneNumber = Utils.normalizeIfPhoneNumber(phoneNumber); + email = validateAndNormaliseEmail(email); + phoneNumber = validateAndNormalisePhoneNumber(phoneNumber); PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, phoneNumber); loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, null, passwordlessLoginMethod)); - } else if (recipeId != null) { - errors.add("Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!"); } } return loginMethods; } - private static void validateTenantId(Main main, AppIdentifier appIdentifier, String tenantId, String recipeId, List errors) + private static String validateAndNormaliseUserRole(String role) { + // We just trim the role the CreateRoleAPI.java + return role.trim(); + } + + private static String validateAndNormaliseTotpSecretKey(String secretKey) { + // We don't perform any normalisation on the secretKey in ImportTotpDeviceAPI.java + return secretKey; + } + + private static Integer validateAndNormaliseTotpPeriod(Number period, List errors) { + // We don't perform any normalisation on the period in ImportTotpDeviceAPI.java other than checking if it is > 0 + if (period != null && period.intValue() < 1) { + errors.add("period should be > 0 for a totp device."); + return null; + } + return period != null ? period.intValue() : null; + } + + private static Integer validateAndNormaliseTotpSkew(Number skew, List errors) { + // We don't perform any normalisation on the period in ImportTotpDeviceAPI.java other than checking if it is >= 0 + if (skew != null && skew.intValue() < 0) { + errors.add("skew should be >= 0 for a totp device."); + return null; + } + return skew != null ? skew.intValue() : null; + } + + private static String validateAndNormaliseTotpDeviceName(String deviceName) { + // We normalise the deviceName as per the ImportTotpDeviceAPI.java + return deviceName != null ? deviceName.trim() : null; + } + + private static void validateAndNormaliseIsPrimaryField(JsonArray jsonLoginMethods, List errors) { + // We are validating that only one loginMethod has isPrimary as true + boolean hasPrimaryLoginMethod = false; + for (JsonElement jsonLoginMethod : jsonLoginMethods) { + JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject(); + if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) { + if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) { + if (hasPrimaryLoginMethod) { + errors.add("No two loginMethods can have isPrimary as true."); + } + hasPrimaryLoginMethod = true; + } + } + } + } + + private static String validateAndNormaliseRecipeId(String recipeId, List errors) { + if (recipeId == null) { + return null; + } + + // We don't perform any normalisation on the recipeId after reading it from request header. + // We will validate it as is. + if (!Arrays.asList("emailpassword", "thirdparty", "passwordless").contains(recipeId)) { + errors.add("Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!"); + } + return recipeId; + } + + private static String validateAndNormaliseTenantId(Main main, AppIdentifier appIdentifier, String tenantId, String recipeId, List errors) throws StorageQueryException, TenantOrAppNotFoundException { if (tenantId == null || tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) { - return; + return tenantId; } if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures()) .noneMatch(t -> t == EE_FEATURES.MULTI_TENANCY)) { errors.add("Multitenancy must be enabled before importing users to a different tenant."); - return; + return tenantId; } + // We make the tenantId lowercase while parsing from the request in WebserverAPI.java + String normalisedTenantId = tenantId.trim().toLowerCase(); TenantConfig[] allTenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main); ArrayList validTenantIds = new ArrayList<>(); Arrays.stream(allTenantConfigs) .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); - if (!validTenantIds.contains(tenantId)) { + if (!validTenantIds.contains(normalisedTenantId)) { errors.add("Invalid tenantId: " + tenantId + " for " + recipeId + " recipe."); } + return normalisedTenantId; } - private static void validatePasswordHashingAlgorithm(Main main, AppIdentifier appIdentifier, EmailPasswordLoginMethod emailPasswordLoginMethod, List errors) throws TenantOrAppNotFoundException { - if(emailPasswordLoginMethod.hashingAlgorithm == null || emailPasswordLoginMethod.passwordHash == null) { - return; - } + private static Boolean validateAndNormaliseIsVerified(Boolean isPrimary) { + // No normalisation needs to be done for isVerified + return isPrimary; + } + + private static long validateAndNormaliseTimeJoined(Number timeJoined) { + // We default timeJoined to 0 if it is null + return timeJoined != null ? timeJoined.longValue() : 0; + } + + private static String validateAndNormaliseEmail(String email) { + // We normalise the email as per the SignUpAPI.java + return email != null ? Utils.normaliseEmail(email) : null; + } + private static String validateAndNormalisePasswordHash(String passwordHash) { + // We trim the passwordHash as per the ImportUserWithPasswordHashAPI.java + return passwordHash != null ? passwordHash.trim() : null; + } + + private static String validateAndNormaliseHashingAlgorithm(Main main, AppIdentifier appIdentifier, String hashingAlgorithm, String passwordHash, List errors) throws TenantOrAppNotFoundException { + if (hashingAlgorithm == null || passwordHash == null) { + return hashingAlgorithm; + } + try { - CoreConfig.PASSWORD_HASHING_ALG passwordHashingAlgorithm = CoreConfig.PASSWORD_HASHING_ALG.valueOf(emailPasswordLoginMethod.hashingAlgorithm); - PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, emailPasswordLoginMethod.passwordHash, passwordHashingAlgorithm); + // We trim the hashingAlgorithm and make it uppercase as per the ImportUserWithPasswordHashAPI.java + CoreConfig.PASSWORD_HASHING_ALG normalisedHashingAlgorithm = CoreConfig.PASSWORD_HASHING_ALG.valueOf(hashingAlgorithm.trim().toUpperCase()); + PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, passwordHash, normalisedHashingAlgorithm); + return normalisedHashingAlgorithm.toString(); } catch (UnsupportedPasswordHashingFormatException e) { errors.add(e.getMessage()); } catch (IllegalArgumentException e) { errors.add("Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"); } + return hashingAlgorithm; + } + + private static String validateAndNormaliseThirdPartyId(String thirdPartyId) { + // We don't perform any normalisation on the thirdPartyId in SignInUpAPI.java + return thirdPartyId; + } + + private static String validateAndNormaliseThirdPartyUserId(String thirdPartyUserId) { + // We don't perform any normalisation on the thirdPartyUserId in SignInUpAPI.java + return thirdPartyUserId; + } + + private static String validateAndNormalisePhoneNumber(String phoneNumber) { + // We normalise the phoneNumber as per the CreateCodeAPI.java + return phoneNumber != null ? Utils.normalizeIfPhoneNumber(phoneNumber) : null; } + } diff --git a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java index e8ee6d569..487315a9c 100644 --- a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java +++ b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java @@ -25,7 +25,7 @@ public class JsonValidatorUtils { @SuppressWarnings("unchecked") - public static T parseAndValidateField(JsonObject jsonObject, String key, ValueType expectedType, + public static T parseAndValidateFieldType(JsonObject jsonObject, String key, ValueType expectedType, boolean isRequired, Class targetType, List errors, String errorSuffix) { if (jsonObject.has(key)) { if (validateJsonFieldType(jsonObject, key, expectedType)) { From 1a178dbab0d9aa37a6ddb84f0c48a35a59cf25a9 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 23 Feb 2024 12:20:12 +0530 Subject: [PATCH 06/23] feat: Add delete bulk import users api --- .github/PULL_REQUEST_TEMPLATE.md | 1 + .../io/supertokens/bulkimport/BulkImport.java | 6 + .../bulkimport/BulkImportUserUtils.java | 37 +- .../supertokens/utils/JsonValidatorUtils.java | 11 +- .../io/supertokens/webserver/Webserver.java | 6 +- .../api/bulkimport/AddBulkImportUsers.java | 113 ------ .../api/bulkimport/BulkImportAPI.java | 217 ++++++++++++ .../api/bulkimport/GetBulkImportUsers.java | 108 ------ .../test/bulkimport/BulkImportTest.java | 330 ++++++++++++++++++ .../test/bulkimport/BulkImportTestUtils.java | 64 ++++ .../apis/AddBulkImportUsersTest.java | 46 +-- .../apis/DeleteFailedBulkImportUsersTest.java | 164 +++++++++ 12 files changed, 835 insertions(+), 268 deletions(-) delete mode 100644 src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java create mode 100644 src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java delete mode 100644 src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java create mode 100644 src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java create mode 100644 src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java create mode 100644 src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 94c0f5211..d2941298f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -35,6 +35,7 @@ highlighting the necessary changes) latest branch (`git branch --all`) whose `X.Y` is greater than the latest released tag. - If no such branch exists, then create one from the latest released branch. - [ ] If added a foreign key constraint on `app_id_to_user_id` table, make sure to delete from this table when deleting the user as well if `deleteUserIdMappingToo` is false. +- [ ] If added a new recipe, then make sure to update the bulk import API to include the new recipe. ## Remaining TODOs for this PR - [ ] Item1 diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index 234c13c3d..b3fc8df17 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -32,8 +32,10 @@ public class BulkImport { + public static final int MAX_USERS_TO_ADD = 10000; public static final int GET_USERS_PAGINATION_LIMIT = 500; public static final int GET_USERS_DEFAULT_LIMIT = 100; + public static final int DELETE_FAILED_USERS_LIMIT = 500; public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, List users) throws StorageQueryException, TenantOrAppNotFoundException { @@ -75,4 +77,8 @@ public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorag List resultUsers = users.subList(0, maxLoop); return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken); } + + public static void deleteUsers(AppIdentifierWithStorage appIdentifierWithStorage, String[] userIds) throws StorageQueryException { + appIdentifierWithStorage.getBulkImportStorage().deleteBulkImportUsers(appIdentifierWithStorage, userIds); + } } diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index d9b2c1f7c..2e82601d2 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -94,18 +94,18 @@ private static List getParsedTotpDevices(JsonObject userData, List getParsedLoginMethods(Main main, AppIdentifier String tenantId = parseAndValidateFieldType(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod."); Boolean isVerified = parseAndValidateFieldType(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); Boolean isPrimary = parseAndValidateFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); - Number timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, false, Number.class, errors, " for a loginMethod"); + Integer timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.INTEGER, false, Integer.class, errors, " for a loginMethod"); recipeId = validateAndNormaliseRecipeId(recipeId, errors); tenantId= validateAndNormaliseTenantId(main, appIdentifier, tenantId, recipeId, errors); + isPrimary = validateAndNormaliseIsPrimary(isPrimary); isVerified = validateAndNormaliseIsVerified(isVerified); + long timeJoinedInMSSinceEpoch = validateAndNormaliseTimeJoined(timeJoined); if ("emailpassword".equals(recipeId)) { @@ -264,12 +266,17 @@ private static String validateAndNormaliseTenantId(Main main, AppIdentifier appI return normalisedTenantId; } - private static Boolean validateAndNormaliseIsVerified(Boolean isPrimary) { - // No normalisation needs to be done for isVerified - return isPrimary; + private static Boolean validateAndNormaliseIsPrimary(Boolean isPrimary) { + // We set the default value as false + return isPrimary == null ? false : isPrimary; + } + + private static Boolean validateAndNormaliseIsVerified(Boolean isVerified) { + // We set the default value as false + return isVerified == null ? false : isVerified; } - private static long validateAndNormaliseTimeJoined(Number timeJoined) { + private static long validateAndNormaliseTimeJoined(Integer timeJoined) { // We default timeJoined to 0 if it is null return timeJoined != null ? timeJoined.longValue() : 0; } diff --git a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java index 487315a9c..beb65a87b 100644 --- a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java +++ b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java @@ -34,8 +34,9 @@ public static T parseAndValidateFieldType(JsonObject jsonObject, String key, case STRING: value = (T) jsonObject.get(key).getAsString(); break; - case NUMBER: - value = (T) jsonObject.get(key).getAsNumber(); + case INTEGER: + Integer intValue = jsonObject.get(key).getAsNumber().intValue(); + value = (T) intValue; break; case BOOLEAN: Boolean boolValue = jsonObject.get(key).getAsBoolean(); @@ -67,7 +68,7 @@ public static T parseAndValidateFieldType(JsonObject jsonObject, String key, public enum ValueType { STRING, - NUMBER, + INTEGER, BOOLEAN, OBJECT, ARRAY_OF_STRING, @@ -77,7 +78,7 @@ public enum ValueType { private static String getTypeForErrorMessage(ValueType type) { return switch (type) { case STRING -> "string"; - case NUMBER -> "number"; + case INTEGER -> "integer"; case BOOLEAN -> "boolean"; case OBJECT -> "object"; case ARRAY_OF_STRING -> "array of string"; @@ -90,7 +91,7 @@ public static boolean validateJsonFieldType(JsonObject jsonObject, String key, V return switch (expectedType) { case STRING -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isString() && !jsonObject.get(key).getAsString().isEmpty(); - case NUMBER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); + case INTEGER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); case BOOLEAN -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isBoolean(); case OBJECT -> jsonObject.get(key).isJsonObject(); case ARRAY_OF_OBJECT, ARRAY_OF_STRING -> jsonObject.get(key).isJsonArray() diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 6f9c30ef1..78059bf13 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -27,8 +27,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.api.accountlinking.*; -import io.supertokens.webserver.api.bulkimport.AddBulkImportUsers; -import io.supertokens.webserver.api.bulkimport.GetBulkImportUsers; +import io.supertokens.webserver.api.bulkimport.BulkImportAPI; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -261,8 +260,7 @@ private void setupRoutes() { addAPI(new RequestStatsAPI(main)); - addAPI(new AddBulkImportUsers(main)); - addAPI(new GetBulkImportUsers(main)); + addAPI(new BulkImportAPI(main)); StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java deleted file mode 100644 index 8239cd0ac..000000000 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2024, 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.bulkimport; - -import io.supertokens.Main; -import io.supertokens.bulkimport.BulkImport; -import io.supertokens.bulkimport.BulkImportUserUtils; -import io.supertokens.multitenancy.exception.BadPermissionException; -import io.supertokens.pluginInterface.bulkimport.BulkImportUser; -import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.utils.Utils; -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.util.ArrayList; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -public class AddBulkImportUsers extends WebserverAPI { - private static final int MAX_USERS_TO_ADD = 10000; - - public AddBulkImportUsers(Main main) { - super(main, ""); - } - - @Override - public String getPath() { - return "/bulk-import/add-users"; - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - JsonObject input = InputParser.parseJsonObjectOrThrowError(req); - JsonArray users = InputParser.parseArrayOrThrowError(input, "users", false); - - if (users.size() <= 0 || users.size() > MAX_USERS_TO_ADD) { - JsonObject errorResponseJson = new JsonObject(); - String errorMsg = users.size() <= 0 ? "You need to add at least one user." - : "You can only add 10000 users at a time."; - errorResponseJson.addProperty("error", errorMsg); - throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); - } - - AppIdentifierWithStorage appIdentifierWithStorage; - try { - appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); - } catch (TenantOrAppNotFoundException | BadPermissionException e) { - throw new ServletException(e); - } - - JsonArray errorsJson = new JsonArray(); - ArrayList usersToAdd = new ArrayList<>(); - - for (int i = 0; i < users.size(); i++) { - try { - BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifierWithStorage, users.get(i).getAsJsonObject(), Utils.getUUID()); - usersToAdd.add(user); - } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { - JsonObject errorObj = new JsonObject(); - - JsonArray errors = e.errors.stream() - .map(JsonPrimitive::new) - .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); - - errorObj.addProperty("index", i); - errorObj.add("errors", errors); - errorsJson.add(errorObj); - } catch (TenantOrAppNotFoundException | StorageQueryException e) { - throw new ServletException(e); - } - } - - if (errorsJson.size() > 0) { - JsonObject errorResponseJson = new JsonObject(); - errorResponseJson.addProperty("error", - "Data has missing or invalid fields. Please check the users field for more details."); - errorResponseJson.add("users", errorsJson); - throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); - } - - try { - BulkImport.addUsers(appIdentifierWithStorage, usersToAdd); - } catch (TenantOrAppNotFoundException | StorageQueryException e) { - throw new ServletException(e); - } - - JsonObject result = new JsonObject(); - result.addProperty("status", "OK"); - super.sendJsonResponse(200, result, resp); - } -} diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java new file mode 100644 index 000000000..0ff586445 --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import java.io.IOException; +import java.util.ArrayList; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.supertokens.Main; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.bulkimport.BulkImportUserPaginationContainer; +import io.supertokens.bulkimport.BulkImportUserPaginationToken; +import io.supertokens.bulkimport.BulkImportUserUtils; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.output.Logging; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.utils.Utils; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class BulkImportAPI extends WebserverAPI { + public BulkImportAPI(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/bulk-import/users"; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String statusString = InputParser.getQueryParamOrThrowError(req, "status", true); + String paginationToken = InputParser.getQueryParamOrThrowError(req, "paginationToken", true); + Integer limit = InputParser.getIntQueryParamOrThrowError(req, "limit", true); + + if (limit != null) { + if (limit > BulkImport.GET_USERS_PAGINATION_LIMIT) { + throw new ServletException( + new BadRequestException("Max limit allowed is " + BulkImport.GET_USERS_PAGINATION_LIMIT)); + } else if (limit < 1) { + throw new ServletException(new BadRequestException("limit must a positive integer with min value 1")); + } + } else { + limit = BulkImport.GET_USERS_DEFAULT_LIMIT; + } + + BulkImportUserStatus status = null; + if (statusString != null) { + try { + status = BulkImportUserStatus.valueOf(statusString); + } catch (IllegalArgumentException e) { + throw new ServletException(new BadRequestException("Invalid value for status. Pass one of NEW, PROCESSING, or FAILED!")); + } + } + + AppIdentifierWithStorage appIdentifierWithStorage = null; + + try { + appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } + + try { + BulkImportUserPaginationContainer users = BulkImport.getUsers(appIdentifierWithStorage, limit, status, paginationToken); + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + + JsonArray usersJson = new JsonArray(); + for (BulkImportUserInfo user : users.users) { + usersJson.add(user.toJsonObject()); + } + result.add("users", usersJson); + + if (users.nextPaginationToken != null) { + result.addProperty("nextPaginationToken", users.nextPaginationToken); + } + super.sendJsonResponse(200, result, resp); + } catch (BulkImportUserPaginationToken.InvalidTokenException e) { + Logging.debug(main, null, Utils.exceptionStacktraceToString(e)); + throw new ServletException(new BadRequestException("invalid pagination token")); + } catch (StorageQueryException e) { + throw new ServletException(e); + } + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + JsonArray users = InputParser.parseArrayOrThrowError(input, "users", false); + + if (users.size() <= 0 || users.size() > BulkImport.MAX_USERS_TO_ADD) { + JsonObject errorResponseJson = new JsonObject(); + String errorMsg = users.size() <= 0 ? "You need to add at least one user." + : "You can only add " + BulkImport.MAX_USERS_TO_ADD + " users at a time."; + errorResponseJson.addProperty("error", errorMsg); + throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); + } + + AppIdentifierWithStorage appIdentifierWithStorage; + try { + appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } + + JsonArray errorsJson = new JsonArray(); + ArrayList usersToAdd = new ArrayList<>(); + + for (int i = 0; i < users.size(); i++) { + try { + BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifierWithStorage, users.get(i).getAsJsonObject(), Utils.getUUID()); + usersToAdd.add(user); + } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { + JsonObject errorObj = new JsonObject(); + + JsonArray errors = e.errors.stream() + .map(JsonPrimitive::new) + .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); + + errorObj.addProperty("index", i); + errorObj.add("errors", errors); + errorsJson.add(errorObj); + } catch (Exception e) { + System.out.println("error: " + e.getMessage()); + e.printStackTrace(); + throw new ServletException(e); + } + } + + if (errorsJson.size() > 0) { + JsonObject errorResponseJson = new JsonObject(); + errorResponseJson.addProperty("error", + "Data has missing or invalid fields. Please check the users field for more details."); + errorResponseJson.add("users", errorsJson); + throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); + } + + try { + BulkImport.addUsers(appIdentifierWithStorage, usersToAdd); + } catch (TenantOrAppNotFoundException | StorageQueryException e) { + throw new ServletException(e); + } + + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + super.sendJsonResponse(200, result, resp); + } + + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + JsonArray arr = InputParser.parseArrayOrThrowError(input, "ids", false); + + if (arr.size() == 0) { + throw new ServletException(new WebserverAPI.BadRequestException("Field name 'ids' cannot be an empty array")); + } + + if (arr.size() > BulkImport.DELETE_FAILED_USERS_LIMIT) { + throw new ServletException(new WebserverAPI.BadRequestException("Field name 'ids' cannot contain more than " + + BulkImport.DELETE_FAILED_USERS_LIMIT + " elements")); + } + + String[] userIds = new String[arr.size()]; + + for (int i = 0; i < userIds.length; i++) { + String userId = InputParser.parseStringFromElementOrThrowError(arr.get(i), "ids", false); + if (userId.isEmpty()) { + throw new ServletException(new WebserverAPI.BadRequestException("Field name 'ids' cannot contain an empty string")); + } + userIds[i] = userId; + } + + AppIdentifierWithStorage appIdentifierWithStorage; + try { + appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } + + try { + BulkImport.deleteUsers(appIdentifierWithStorage, userIds); + } catch (StorageQueryException e) { + throw new ServletException(e); + } + + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + super.sendJsonResponse(200, result, resp); + } +} diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java deleted file mode 100644 index f89fe39c3..000000000 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2024, 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.bulkimport; - -import java.io.IOException; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; - -import io.supertokens.Main; -import io.supertokens.bulkimport.BulkImport; -import io.supertokens.bulkimport.BulkImportUserPaginationContainer; -import io.supertokens.bulkimport.BulkImportUserPaginationToken; -import io.supertokens.multitenancy.exception.BadPermissionException; -import io.supertokens.output.Logging; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; -import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; -import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.utils.Utils; -import io.supertokens.webserver.InputParser; -import io.supertokens.webserver.WebserverAPI; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public class GetBulkImportUsers extends WebserverAPI { - public GetBulkImportUsers(Main main) { - super(main, ""); - } - - @Override - public String getPath() { - return "/bulk-import/users"; - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String statusString = InputParser.getQueryParamOrThrowError(req, "status", true); - String paginationToken = InputParser.getQueryParamOrThrowError(req, "paginationToken", true); - Integer limit = InputParser.getIntQueryParamOrThrowError(req, "limit", true); - - if (limit != null) { - if (limit > BulkImport.GET_USERS_PAGINATION_LIMIT) { - throw new ServletException( - new BadRequestException("Max limit allowed is " + BulkImport.GET_USERS_PAGINATION_LIMIT)); - } else if (limit < 1) { - throw new ServletException(new BadRequestException("limit must a positive integer with min value 1")); - } - } else { - limit = BulkImport.GET_USERS_DEFAULT_LIMIT; - } - - BulkImportUserStatus status = null; - if (statusString != null) { - try { - status = BulkImportUserStatus.valueOf(statusString); - } catch (IllegalArgumentException e) { - throw new ServletException(new BadRequestException("Invalid value for status. Pass one of NEW, PROCESSING, or FAILED!")); - } - } - - AppIdentifierWithStorage appIdentifierWithStorage = null; - - try { - appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); - } catch (TenantOrAppNotFoundException | BadPermissionException e) { - throw new ServletException(e); - } - - try { - BulkImportUserPaginationContainer users = BulkImport.getUsers(appIdentifierWithStorage, limit, status, paginationToken); - JsonObject result = new JsonObject(); - result.addProperty("status", "OK"); - - JsonArray usersJson = new JsonArray(); - for (BulkImportUserInfo user : users.users) { - usersJson.add(user.toJsonObject()); - } - result.add("users", usersJson); - - if (users.nextPaginationToken != null) { - result.addProperty("nextPaginationToken", users.nextPaginationToken); - } - super.sendJsonResponse(200, result, resp); - } catch (BulkImportUserPaginationToken.InvalidTokenException e) { - Logging.debug(main, null, Utils.exceptionStacktraceToString(e)); - throw new ServletException(new BadRequestException("invalid pagination token")); - } catch (StorageQueryException e) { - throw new ServletException(e); - } - } -} diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java new file mode 100644 index 000000000..229d1263a --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import io.supertokens.ProcessState; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.bulkimport.BulkImportUserPaginationContainer; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; +import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; + +import static io.supertokens.test.bulkimport.BulkImportTestUtils.generateBulkImportUser; + +public class BulkImportTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void shouldAddUsersInBulkImportUsersTable() 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; + } + + List users = generateBulkImportUser(10); + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + BulkImport.addUsers(new AppIdentifierWithStorage(null, null, storage), users); + + List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, BulkImportUserStatus.NEW, null, null); + + // Verify that all users are present in addedUsers + for (BulkImportUser user : users) { + BulkImportUserInfo matchingUser = addedUsers.stream() + .filter(addedUser -> user.id.equals(addedUser.id)) + .findFirst() + .orElse(null); + + assertNotNull(matchingUser); + assertEquals(BulkImportUserStatus.NEW, matchingUser.status); + assertEquals(user.toString(), matchingUser.rawData); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldCreatedNewIdsIfDuplicateIdIsFound() 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; + } + + List users = generateBulkImportUser(10); + + // We are setting the id of the second user to be the same as the first user to ensure a duplicate id is present + users.get(1).id = users.get(0).id; + + List initialIds = users.stream().map(user -> user.id).collect(Collectors.toList()); + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); + BulkImport.addUsers(appIdentifierWithStorage, users); + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + + // Verify that the other properties are same but ids changed + for (BulkImportUser user : users) { + BulkImportUserInfo matchingUser = addedUsers.stream() + .filter(addedUser -> user.toString().equals(addedUser.rawData)) + .findFirst() + .orElse(null); + + assertNotNull(matchingUser); + assertEquals(BulkImportUserStatus.NEW, matchingUser.status); + assertFalse(initialIds.contains(matchingUser.id)); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testGetUsersStatusFilter() 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; + } + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); + + // Test with status = 'NEW' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifierWithStorage, users); + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + assertEquals(10, addedUsers.size()); + } + + // Test with status = 'PROCESSING' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifierWithStorage, users); + + // Update the users status to PROCESSING + for (BulkImportUser user : users) { + storage.updateBulkImportUserStatus(appIdentifierWithStorage, user.id, BulkImportUserStatus.PROCESSING); + } + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.PROCESSING, null, null); + assertEquals(10, addedUsers.size()); + } + + // Test with status = 'FAILED' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifierWithStorage, users); + + // Update the users status to FAILED + for (BulkImportUser user : users) { + storage.updateBulkImportUserStatus(appIdentifierWithStorage, user.id, BulkImportUserStatus.FAILED); + } + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.FAILED, null, null); + assertEquals(10, addedUsers.size()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void randomPaginationTest() 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; + } + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + + int numberOfUsers = 500; + // Insert users in batches + { + int batchSize = 100; + for (int i = 0; i < numberOfUsers; i += batchSize) { + List users = generateBulkImportUser(batchSize); + BulkImport.addUsers(new AppIdentifierWithStorage(null, null, storage), users); + // Adding a delay between each batch to ensure the createdAt different + Thread.sleep(1000); + } + } + + // Get all inserted users + List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, null, null, null); + assertEquals(numberOfUsers, addedUsers.size()); + + // We are sorting the users based on createdAt and id like we do in the storage layer + List sortedUsers = addedUsers.stream() + .sorted((user1, user2) -> { + int compareResult = user2.createdAt.compareTo(user1.createdAt); + if (compareResult == 0) { + return user2.id.compareTo(user1.id); + } + return compareResult; + }) + .collect(Collectors.toList()); + + int[] limits = new int[]{10, 14, 20, 23, 50, 100, 110, 150, 200, 510}; + + for (int limit : limits) { + int indexIntoUsers = 0; + String paginationToken = null; + do { + BulkImportUserPaginationContainer users = BulkImport.getUsers(new AppIdentifierWithStorage(null, null, storage), limit, null, paginationToken); + + for (BulkImportUserInfo actualUser : users.users) { + BulkImportUserInfo expectedUser = sortedUsers.get(indexIntoUsers); + + assertEquals(expectedUser.id, actualUser.id); + assertEquals(expectedUser.rawData, actualUser.rawData); + indexIntoUsers++; + } + + paginationToken = users.nextPaginationToken; + } while (paginationToken != null); + + assert (indexIntoUsers == sortedUsers.size()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldDeleteUsersFromTheBulkImportUsersTable() 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; + } + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); + + // Test with status = 'NEW' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifierWithStorage, users); + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + assertEquals(10, addedUsers.size()); + + String userIds[] = addedUsers.stream().map(user -> user.id).toArray(String[]::new); + BulkImport.deleteUsers(appIdentifierWithStorage, userIds); + + List usersAfterDelete = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + assertEquals(0, usersAfterDelete.size()); + } + + // Test with status = 'PROCESSING' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifierWithStorage, users); + + // Update the users status to PROCESSING + for (BulkImportUser user : users) { + storage.updateBulkImportUserStatus(appIdentifierWithStorage, user.id, BulkImportUserStatus.PROCESSING); + } + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.PROCESSING, null, null); + assertEquals(10, addedUsers.size()); + + String userIds[] = addedUsers.stream().map(user -> user.id).toArray(String[]::new); + BulkImport.deleteUsers(appIdentifierWithStorage, userIds); + + List usersAfterDelete = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.PROCESSING, null, null); + assertEquals(0, usersAfterDelete.size()); + } + + // Test with status = 'FAILED' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifierWithStorage, users); + + // Update the users status to FAILED + for (BulkImportUser user : users) { + storage.updateBulkImportUserStatus(appIdentifierWithStorage, user.id, BulkImportUserStatus.FAILED); + } + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.FAILED, null, null); + assertEquals(10, addedUsers.size()); + + String userIds[] = addedUsers.stream().map(user -> user.id).toArray(String[]::new); + BulkImport.deleteUsers(appIdentifierWithStorage, userIds); + + List usersAfterDelete = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.PROCESSING, null, null); + assertEquals(0, usersAfterDelete.size()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java new file mode 100644 index 000000000..fd33fb32d --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonObject; + +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.EmailPasswordLoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.PasswordlessLoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.ThirdPartyLoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; + +public class BulkImportTestUtils { + public static List generateBulkImportUser(int numberOfUsers) { + List users = new ArrayList<>(); + + for (int i = 0; i < numberOfUsers; i++) { + String email = "user" + i + "@example.com"; + String id = io.supertokens.utils.Utils.getUUID(); + String externalId = io.supertokens.utils.Utils.getUUID(); + + JsonObject userMetadata = new JsonObject(); + userMetadata.addProperty("key", "value"); + + List userRoles = new ArrayList<>(); + userRoles.add("role1"); + userRoles.add("role2"); + + List totpDevices = new ArrayList<>(); + totpDevices.add(new TotpDevice("secretKey", 30, 1, "deviceName")); + + EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, "$2a", "BCRYPT"); + ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, "thirdPartyId", "thirdPartyUserId"); + PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, "+911234567890"); + + + List loginMethods = new ArrayList<>(); + loginMethods.add(new LoginMethod("public", "emailpassword", true, true, 0, emailPasswordLoginMethod, null, null)); + loginMethods.add(new LoginMethod("public", "thirdparty", true, false, 0, null, thirdPartyLoginMethod, null)); + loginMethods.add(new LoginMethod("public", "passwordless", true, false, 0, null, null, passwordlessLoginMethod)); + users.add(new BulkImportUser(id, externalId, userMetadata, userRoles, totpDevices, loginMethods)); + } + return users; + } +} diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index f20bceb42..376f4b3ff 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -79,7 +79,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = new JsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -90,7 +90,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = new JsonParser().parse("{\"users\": \"string\"}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -104,7 +104,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = new JsonParser().parse("{\"users\":[{}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -116,7 +116,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser().parse("{\"users\":[{\"loginMethods\": \"string\"}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -128,7 +128,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = new JsonParser().parse("{\"users\":[{\"loginMethods\": []}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -143,7 +143,7 @@ public void shouldThrow400Error() throws Exception { .parse("{\"users\":[{\"externalUserId\":[],\"userMetaData\":[],\"roles\":{},\"totp\":{}}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -159,13 +159,13 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":[],\"tenantId\":[],\"isPrimary\":[],\"isVerified\":[],\"timeJoinedInMSSinceEpoch\":[]}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); assertEquals(responseString, - "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"recipeId should be of type string for a loginMethod.\",\"tenantId should be of type string for a loginMethod.\",\"isVerified should be of type boolean for a loginMethod.\",\"isPrimary should be of type boolean for a loginMethod.\",\"timeJoinedInMSSinceEpoch should be of type number for a loginMethod\"]}]}"); + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"recipeId should be of type string for a loginMethod.\",\"tenantId should be of type string for a loginMethod.\",\"isVerified should be of type boolean for a loginMethod.\",\"isPrimary should be of type boolean for a loginMethod.\",\"timeJoinedInMSSinceEpoch should be of type integer for a loginMethod\"]}]}"); } } // Invalid recipeId @@ -175,7 +175,7 @@ public void shouldThrow400Error() throws Exception { .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"invalid_recipe_id\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -191,7 +191,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser() .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\"}]}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -205,7 +205,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":[],\"passwordHash\":[],\"hashingAlgorithm\":[]}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -219,7 +219,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"$2a\",\"hashingAlgorithm\":\"invalid_algorithm\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -235,7 +235,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser() .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"thirdparty\"}]}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -249,7 +249,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"thirdparty\",\"email\":[],\"thirdPartyId\":[],\"thirdPartyUserId\":[]}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -265,7 +265,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser() .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"passwordless\"}]}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -279,7 +279,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"passwordless\",\"email\":[],\"phoneNumber\":[]}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -296,7 +296,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -313,7 +313,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -329,7 +329,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser() .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"$2a\",\"hashingAlgorithm\":\"bcrypt\",\"isPrimary\":true},{\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\",\"isPrimary\":true}]}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -343,7 +343,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = generateUsersJson(0); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -356,7 +356,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = generateUsersJson(10001); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -382,7 +382,7 @@ public void shouldReturn200Response() throws Exception { JsonObject request = generateUsersJson(10000); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 10000, null, Utils.getCdiVersionStringLatestForTests(), null); assertEquals("OK", response.get("status").getAsString()); @@ -403,7 +403,7 @@ public void shouldNormaliseFields() throws Exception { JsonObject request = generateUsersJson(1); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); assertEquals("OK", response.get("status").getAsString()); diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java new file mode 100644 index 000000000..0e8ff9fdf --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2024, 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.bulkimport.apis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.List; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; + +import io.supertokens.ProcessState; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; + +import static io.supertokens.test.bulkimport.BulkImportTestUtils.generateBulkImportUser; + +public class DeleteFailedBulkImportUsersTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void shouldReturn400Error() 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; + } + + { + try { + JsonObject request = new JsonObject(); + HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Field name 'ids' is invalid in JSON input", e.getMessage()); + } + } + { + try { + JsonObject request = new JsonParser().parse("{\"ids\":[]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Field name 'ids' cannot be an empty array", e.getMessage()); + } + } + { + try { + JsonObject request = new JsonParser().parse("{\"ids\":[\"\"]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Field name 'ids' cannot contain an empty string", e.getMessage()); + } + } + { + try { + // Create a string array of 500 uuids + JsonObject request = new JsonObject(); + JsonArray ids = new JsonArray(); + for (int i = 0; i < 501; i++) { + ids.add(new JsonPrimitive(io.supertokens.utils.Utils.getUUID())); + } + request.add("ids", ids); + + HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Field name 'ids' cannot contain more than 500 elements", e.getMessage()); + } + } + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldReturn200Response() 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; + } + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); + + // Insert users + List users = generateBulkImportUser(5); + BulkImport.addUsers(appIdentifierWithStorage, users); + + JsonObject request = new JsonObject(); + JsonArray ids = new JsonArray(); + for (BulkImportUser user : users) { + ids.add(new JsonPrimitive(user.id)); + } + request.add("ids", ids); + + JsonObject response = HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 10000, null, Utils.getCdiVersionStringLatestForTests(), null); + assertEquals("OK", response.get("status").getAsString()); + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} From 4b34b89d437814c879ec6b43d6879fe5dde29dcc Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Fri, 23 Feb 2024 18:42:33 +0530 Subject: [PATCH 07/23] fix: PR changes --- .../io/supertokens/bulkimport/BulkImport.java | 1 + .../bulkimport/BulkImportUserUtils.java | 74 +++-- .../supertokens/utils/JsonValidatorUtils.java | 11 +- .../io/supertokens/webserver/Webserver.java | 6 +- .../api/bulkimport/AddBulkImportUsers.java | 113 -------- ...ulkImportUsers.java => BulkImportAPI.java} | 71 ++++- .../test/bulkimport/BulkImportTest.java | 266 ++++++++++++++++++ .../test/bulkimport/BulkImportTestUtils.java | 63 +++++ .../apis/AddBulkImportUsersTest.java | 46 +-- .../apis/DeleteFailedBulkImportUsersTest.java | 164 +++++++++++ .../apis/GetBulkImportUsersTest.java | 2 +- 11 files changed, 640 insertions(+), 177 deletions(-) delete mode 100644 src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java rename src/main/java/io/supertokens/webserver/api/bulkimport/{GetBulkImportUsers.java => BulkImportAPI.java} (58%) create mode 100644 src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java create mode 100644 src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java create mode 100644 src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index 234c13c3d..49e539bfc 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -32,6 +32,7 @@ public class BulkImport { + public static final int MAX_USERS_TO_ADD = 10000; public static final int GET_USERS_PAGINATION_LIMIT = 500; public static final int GET_USERS_DEFAULT_LIMIT = 100; diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index d9b2c1f7c..e5c107bd8 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -94,18 +94,18 @@ private static List getParsedTotpDevices(JsonObject userData, List getParsedLoginMethods(Main main, AppIdentifier String tenantId = parseAndValidateFieldType(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod."); Boolean isVerified = parseAndValidateFieldType(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); Boolean isPrimary = parseAndValidateFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); - Number timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.NUMBER, false, Number.class, errors, " for a loginMethod"); + Integer timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.INTEGER, false, Integer.class, errors, " for a loginMethod"); recipeId = validateAndNormaliseRecipeId(recipeId, errors); tenantId= validateAndNormaliseTenantId(main, appIdentifier, tenantId, recipeId, errors); + isPrimary = validateAndNormaliseIsPrimary(isPrimary); isVerified = validateAndNormaliseIsVerified(isVerified); + long timeJoinedInMSSinceEpoch = validateAndNormaliseTimeJoined(timeJoined); if ("emailpassword".equals(recipeId)) { @@ -147,8 +149,9 @@ private static List getParsedLoginMethods(Main main, AppIdentifier String hashingAlgorithm = parseAndValidateFieldType(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); email = validateAndNormaliseEmail(email); - passwordHash = validateAndNormalisePasswordHash(passwordHash); - hashingAlgorithm = validateAndNormaliseHashingAlgorithm(main, appIdentifier, hashingAlgorithm, passwordHash, errors); + CoreConfig.PASSWORD_HASHING_ALG normalisedHashingAlgorithm = validateAndNormaliseHashingAlgorithm(hashingAlgorithm, errors); + hashingAlgorithm = normalisedHashingAlgorithm != null ? normalisedHashingAlgorithm.toString() : hashingAlgorithm; + passwordHash = validateAndNormalisePasswordHash(main, appIdentifier, normalisedHashingAlgorithm, passwordHash, errors); EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, passwordHash, hashingAlgorithm); loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, emailPasswordLoginMethod, null, null)); @@ -264,12 +267,17 @@ private static String validateAndNormaliseTenantId(Main main, AppIdentifier appI return normalisedTenantId; } - private static Boolean validateAndNormaliseIsVerified(Boolean isPrimary) { - // No normalisation needs to be done for isVerified - return isPrimary; + private static Boolean validateAndNormaliseIsPrimary(Boolean isPrimary) { + // We set the default value as false + return isPrimary == null ? false : isPrimary; + } + + private static Boolean validateAndNormaliseIsVerified(Boolean isVerified) { + // We set the default value as false + return isVerified == null ? false : isVerified; } - private static long validateAndNormaliseTimeJoined(Number timeJoined) { + private static long validateAndNormaliseTimeJoined(Integer timeJoined) { // We default timeJoined to 0 if it is null return timeJoined != null ? timeJoined.longValue() : 0; } @@ -279,27 +287,35 @@ private static String validateAndNormaliseEmail(String email) { return email != null ? Utils.normaliseEmail(email) : null; } - private static String validateAndNormalisePasswordHash(String passwordHash) { - // We trim the passwordHash as per the ImportUserWithPasswordHashAPI.java - return passwordHash != null ? passwordHash.trim() : null; + private static CoreConfig.PASSWORD_HASHING_ALG validateAndNormaliseHashingAlgorithm(String hashingAlgorithm, List errors) { + if (hashingAlgorithm == null) { + return null; + } + + try { + // We trim the hashingAlgorithm and make it uppercase as per the ImportUserWithPasswordHashAPI.java + return CoreConfig.PASSWORD_HASHING_ALG.valueOf(hashingAlgorithm.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + errors.add("Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"); + return null; + } } - private static String validateAndNormaliseHashingAlgorithm(Main main, AppIdentifier appIdentifier, String hashingAlgorithm, String passwordHash, List errors) throws TenantOrAppNotFoundException { + private static String validateAndNormalisePasswordHash(Main main, AppIdentifier appIdentifier, CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm, String passwordHash, List errors) throws TenantOrAppNotFoundException { if (hashingAlgorithm == null || passwordHash == null) { - return hashingAlgorithm; + return passwordHash; } + // We trim the passwordHash and validate it as per ImportUserWithPasswordHashAPI.java + passwordHash = passwordHash.trim(); + try { - // We trim the hashingAlgorithm and make it uppercase as per the ImportUserWithPasswordHashAPI.java - CoreConfig.PASSWORD_HASHING_ALG normalisedHashingAlgorithm = CoreConfig.PASSWORD_HASHING_ALG.valueOf(hashingAlgorithm.trim().toUpperCase()); - PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, passwordHash, normalisedHashingAlgorithm); - return normalisedHashingAlgorithm.toString(); + PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, passwordHash, hashingAlgorithm); } catch (UnsupportedPasswordHashingFormatException e) { errors.add(e.getMessage()); - } catch (IllegalArgumentException e) { - errors.add("Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"); } - return hashingAlgorithm; + + return passwordHash; } private static String validateAndNormaliseThirdPartyId(String thirdPartyId) { diff --git a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java index 487315a9c..beb65a87b 100644 --- a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java +++ b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java @@ -34,8 +34,9 @@ public static T parseAndValidateFieldType(JsonObject jsonObject, String key, case STRING: value = (T) jsonObject.get(key).getAsString(); break; - case NUMBER: - value = (T) jsonObject.get(key).getAsNumber(); + case INTEGER: + Integer intValue = jsonObject.get(key).getAsNumber().intValue(); + value = (T) intValue; break; case BOOLEAN: Boolean boolValue = jsonObject.get(key).getAsBoolean(); @@ -67,7 +68,7 @@ public static T parseAndValidateFieldType(JsonObject jsonObject, String key, public enum ValueType { STRING, - NUMBER, + INTEGER, BOOLEAN, OBJECT, ARRAY_OF_STRING, @@ -77,7 +78,7 @@ public enum ValueType { private static String getTypeForErrorMessage(ValueType type) { return switch (type) { case STRING -> "string"; - case NUMBER -> "number"; + case INTEGER -> "integer"; case BOOLEAN -> "boolean"; case OBJECT -> "object"; case ARRAY_OF_STRING -> "array of string"; @@ -90,7 +91,7 @@ public static boolean validateJsonFieldType(JsonObject jsonObject, String key, V return switch (expectedType) { case STRING -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isString() && !jsonObject.get(key).getAsString().isEmpty(); - case NUMBER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); + case INTEGER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); case BOOLEAN -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isBoolean(); case OBJECT -> jsonObject.get(key).isJsonObject(); case ARRAY_OF_OBJECT, ARRAY_OF_STRING -> jsonObject.get(key).isJsonArray() diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 6f9c30ef1..78059bf13 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -27,8 +27,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.api.accountlinking.*; -import io.supertokens.webserver.api.bulkimport.AddBulkImportUsers; -import io.supertokens.webserver.api.bulkimport.GetBulkImportUsers; +import io.supertokens.webserver.api.bulkimport.BulkImportAPI; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -261,8 +260,7 @@ private void setupRoutes() { addAPI(new RequestStatsAPI(main)); - addAPI(new AddBulkImportUsers(main)); - addAPI(new GetBulkImportUsers(main)); + addAPI(new BulkImportAPI(main)); StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java deleted file mode 100644 index 8239cd0ac..000000000 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2024, 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.bulkimport; - -import io.supertokens.Main; -import io.supertokens.bulkimport.BulkImport; -import io.supertokens.bulkimport.BulkImportUserUtils; -import io.supertokens.multitenancy.exception.BadPermissionException; -import io.supertokens.pluginInterface.bulkimport.BulkImportUser; -import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.utils.Utils; -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.util.ArrayList; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -public class AddBulkImportUsers extends WebserverAPI { - private static final int MAX_USERS_TO_ADD = 10000; - - public AddBulkImportUsers(Main main) { - super(main, ""); - } - - @Override - public String getPath() { - return "/bulk-import/add-users"; - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - JsonObject input = InputParser.parseJsonObjectOrThrowError(req); - JsonArray users = InputParser.parseArrayOrThrowError(input, "users", false); - - if (users.size() <= 0 || users.size() > MAX_USERS_TO_ADD) { - JsonObject errorResponseJson = new JsonObject(); - String errorMsg = users.size() <= 0 ? "You need to add at least one user." - : "You can only add 10000 users at a time."; - errorResponseJson.addProperty("error", errorMsg); - throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); - } - - AppIdentifierWithStorage appIdentifierWithStorage; - try { - appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); - } catch (TenantOrAppNotFoundException | BadPermissionException e) { - throw new ServletException(e); - } - - JsonArray errorsJson = new JsonArray(); - ArrayList usersToAdd = new ArrayList<>(); - - for (int i = 0; i < users.size(); i++) { - try { - BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifierWithStorage, users.get(i).getAsJsonObject(), Utils.getUUID()); - usersToAdd.add(user); - } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { - JsonObject errorObj = new JsonObject(); - - JsonArray errors = e.errors.stream() - .map(JsonPrimitive::new) - .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); - - errorObj.addProperty("index", i); - errorObj.add("errors", errors); - errorsJson.add(errorObj); - } catch (TenantOrAppNotFoundException | StorageQueryException e) { - throw new ServletException(e); - } - } - - if (errorsJson.size() > 0) { - JsonObject errorResponseJson = new JsonObject(); - errorResponseJson.addProperty("error", - "Data has missing or invalid fields. Please check the users field for more details."); - errorResponseJson.add("users", errorsJson); - throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); - } - - try { - BulkImport.addUsers(appIdentifierWithStorage, usersToAdd); - } catch (TenantOrAppNotFoundException | StorageQueryException e) { - throw new ServletException(e); - } - - JsonObject result = new JsonObject(); - result.addProperty("status", "OK"); - super.sendJsonResponse(200, result, resp); - } -} diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java similarity index 58% rename from src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java rename to src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java index f89fe39c3..9c6d64310 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java @@ -17,17 +17,21 @@ package io.supertokens.webserver.api.bulkimport; import java.io.IOException; +import java.util.ArrayList; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import io.supertokens.Main; import io.supertokens.bulkimport.BulkImport; import io.supertokens.bulkimport.BulkImportUserPaginationContainer; import io.supertokens.bulkimport.BulkImportUserPaginationToken; +import io.supertokens.bulkimport.BulkImportUserUtils; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -39,8 +43,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -public class GetBulkImportUsers extends WebserverAPI { - public GetBulkImportUsers(Main main) { +public class BulkImportAPI extends WebserverAPI { + public BulkImportAPI(Main main) { super(main, ""); } @@ -105,4 +109,67 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se throw new ServletException(e); } } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + JsonArray users = InputParser.parseArrayOrThrowError(input, "users", false); + + if (users.size() <= 0 || users.size() > BulkImport.MAX_USERS_TO_ADD) { + JsonObject errorResponseJson = new JsonObject(); + String errorMsg = users.size() <= 0 ? "You need to add at least one user." + : "You can only add " + BulkImport.MAX_USERS_TO_ADD + " users at a time."; + errorResponseJson.addProperty("error", errorMsg); + throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); + } + + AppIdentifierWithStorage appIdentifierWithStorage; + try { + appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } + + JsonArray errorsJson = new JsonArray(); + ArrayList usersToAdd = new ArrayList<>(); + + for (int i = 0; i < users.size(); i++) { + try { + BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifierWithStorage, users.get(i).getAsJsonObject(), Utils.getUUID()); + usersToAdd.add(user); + } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { + JsonObject errorObj = new JsonObject(); + + JsonArray errors = e.errors.stream() + .map(JsonPrimitive::new) + .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); + + errorObj.addProperty("index", i); + errorObj.add("errors", errors); + errorsJson.add(errorObj); + } catch (Exception e) { + System.out.println("error: " + e.getMessage()); + e.printStackTrace(); + throw new ServletException(e); + } + } + + if (errorsJson.size() > 0) { + JsonObject errorResponseJson = new JsonObject(); + errorResponseJson.addProperty("error", + "Data has missing or invalid fields. Please check the users field for more details."); + errorResponseJson.add("users", errorsJson); + throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); + } + + try { + BulkImport.addUsers(appIdentifierWithStorage, usersToAdd); + } catch (TenantOrAppNotFoundException | StorageQueryException e) { + throw new ServletException(e); + } + + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + super.sendJsonResponse(200, result, resp); + } } diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java new file mode 100644 index 000000000..6d9b327c5 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import io.supertokens.ProcessState; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.bulkimport.BulkImportUserPaginationContainer; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; +import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; + +import static io.supertokens.test.bulkimport.BulkImportTestUtils.generateBulkImportUser; + +public class BulkImportTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void shouldAddUsersInBulkImportUsersTable() 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; + } + + List users = generateBulkImportUser(10); + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + BulkImport.addUsers(new AppIdentifierWithStorage(null, null, storage), users); + + List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, BulkImportUserStatus.NEW, null, null); + + // Verify that all users are present in addedUsers + for (BulkImportUser user : users) { + BulkImportUserInfo matchingUser = addedUsers.stream() + .filter(addedUser -> user.id.equals(addedUser.id)) + .findFirst() + .orElse(null); + + assertNotNull(matchingUser); + assertEquals(BulkImportUserStatus.NEW, matchingUser.status); + assertEquals(user.toString(), matchingUser.rawData); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldCreatedNewIdsIfDuplicateIdIsFound() 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; + } + + List users = generateBulkImportUser(10); + + // We are setting the id of the second user to be the same as the first user to ensure a duplicate id is present + users.get(1).id = users.get(0).id; + + List initialIds = users.stream().map(user -> user.id).collect(Collectors.toList()); + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); + BulkImport.addUsers(appIdentifierWithStorage, users); + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + + // Verify that the other properties are same but ids changed + for (BulkImportUser user : users) { + BulkImportUserInfo matchingUser = addedUsers.stream() + .filter(addedUser -> user.toString().equals(addedUser.rawData)) + .findFirst() + .orElse(null); + + assertNotNull(matchingUser); + assertEquals(BulkImportUserStatus.NEW, matchingUser.status); + assertFalse(initialIds.contains(matchingUser.id)); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testGetUsersStatusFilter() 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; + } + + BulkImportSQLStorage storage = (BulkImportSQLStorage) StorageLayer.getStorage(process.main); + AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); + + // Test with status = 'NEW' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifierWithStorage, users); + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + assertEquals(10, addedUsers.size()); + } + + // Test with status = 'PROCESSING' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifierWithStorage, users); + + // Update the users status to PROCESSING + String[] userIds = users.stream().map(user -> user.id).toArray(String[]::new); + + storage.startTransaction(con -> { + storage.updateBulkImportUserStatus_Transaction(appIdentifierWithStorage, con, userIds, BulkImportUserStatus.PROCESSING); + storage.commitTransaction(con); + return null; + }); + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.PROCESSING, null, null); + assertEquals(10, addedUsers.size()); + } + + // Test with status = 'FAILED' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifierWithStorage, users); + + // Update the users status to FAILED + String[] userIds = users.stream().map(user -> user.id).toArray(String[]::new); + + storage.startTransaction(con -> { + storage.updateBulkImportUserStatus_Transaction(appIdentifierWithStorage, con, userIds, BulkImportUserStatus.FAILED); + storage.commitTransaction(con); + return null; + }); + + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.FAILED, null, null); + assertEquals(10, addedUsers.size()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void randomPaginationTest() 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; + } + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + + int numberOfUsers = 500; + // Insert users in batches + { + int batchSize = 100; + for (int i = 0; i < numberOfUsers; i += batchSize) { + List users = generateBulkImportUser(batchSize); + BulkImport.addUsers(new AppIdentifierWithStorage(null, null, storage), users); + // Adding a delay between each batch to ensure the createdAt different + Thread.sleep(1000); + } + } + + // Get all inserted users + List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, null, null, null); + assertEquals(numberOfUsers, addedUsers.size()); + + // We are sorting the users based on createdAt and id like we do in the storage layer + List sortedUsers = addedUsers.stream() + .sorted((user1, user2) -> { + int compareResult = user2.createdAt.compareTo(user1.createdAt); + if (compareResult == 0) { + return user2.id.compareTo(user1.id); + } + return compareResult; + }) + .collect(Collectors.toList()); + + int[] limits = new int[]{10, 14, 20, 23, 50, 100, 110, 150, 200, 510}; + + for (int limit : limits) { + int indexIntoUsers = 0; + String paginationToken = null; + do { + BulkImportUserPaginationContainer users = BulkImport.getUsers(new AppIdentifierWithStorage(null, null, storage), limit, null, paginationToken); + + for (BulkImportUserInfo actualUser : users.users) { + BulkImportUserInfo expectedUser = sortedUsers.get(indexIntoUsers); + + assertEquals(expectedUser.id, actualUser.id); + assertEquals(expectedUser.rawData, actualUser.rawData); + indexIntoUsers++; + } + + paginationToken = users.nextPaginationToken; + } while (paginationToken != null); + + assert (indexIntoUsers == sortedUsers.size()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java new file mode 100644 index 000000000..86952977f --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonObject; + +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.EmailPasswordLoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.PasswordlessLoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.ThirdPartyLoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; + +public class BulkImportTestUtils { + public static List generateBulkImportUser(int numberOfUsers) { + List users = new ArrayList<>(); + + for (int i = 0; i < numberOfUsers; i++) { + String email = "user" + i + "@example.com"; + String id = io.supertokens.utils.Utils.getUUID(); + String externalId = io.supertokens.utils.Utils.getUUID(); + + JsonObject userMetadata = new JsonObject(); + userMetadata.addProperty("key", "value"); + + List userRoles = new ArrayList<>(); + userRoles.add("role1"); + userRoles.add("role2"); + + List totpDevices = new ArrayList<>(); + totpDevices.add(new TotpDevice("secretKey", 30, 1, "deviceName")); + + EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, "$2a", "BCRYPT"); + ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, "thirdPartyId", "thirdPartyUserId"); + PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, "+911234567890"); + + List loginMethods = new ArrayList<>(); + loginMethods.add(new LoginMethod("public", "emailpassword", true, true, 0, emailPasswordLoginMethod, null, null)); + loginMethods.add(new LoginMethod("public", "thirdparty", true, false, 0, null, thirdPartyLoginMethod, null)); + loginMethods.add(new LoginMethod("public", "passwordless", true, false, 0, null, null, passwordlessLoginMethod)); + users.add(new BulkImportUser(id, externalId, userMetadata, userRoles, totpDevices, loginMethods)); + } + return users; + } +} diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index f20bceb42..376f4b3ff 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -79,7 +79,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = new JsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -90,7 +90,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = new JsonParser().parse("{\"users\": \"string\"}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -104,7 +104,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = new JsonParser().parse("{\"users\":[{}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -116,7 +116,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser().parse("{\"users\":[{\"loginMethods\": \"string\"}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -128,7 +128,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = new JsonParser().parse("{\"users\":[{\"loginMethods\": []}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -143,7 +143,7 @@ public void shouldThrow400Error() throws Exception { .parse("{\"users\":[{\"externalUserId\":[],\"userMetaData\":[],\"roles\":{},\"totp\":{}}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -159,13 +159,13 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":[],\"tenantId\":[],\"isPrimary\":[],\"isVerified\":[],\"timeJoinedInMSSinceEpoch\":[]}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); assertEquals(responseString, - "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"recipeId should be of type string for a loginMethod.\",\"tenantId should be of type string for a loginMethod.\",\"isVerified should be of type boolean for a loginMethod.\",\"isPrimary should be of type boolean for a loginMethod.\",\"timeJoinedInMSSinceEpoch should be of type number for a loginMethod\"]}]}"); + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"recipeId should be of type string for a loginMethod.\",\"tenantId should be of type string for a loginMethod.\",\"isVerified should be of type boolean for a loginMethod.\",\"isPrimary should be of type boolean for a loginMethod.\",\"timeJoinedInMSSinceEpoch should be of type integer for a loginMethod\"]}]}"); } } // Invalid recipeId @@ -175,7 +175,7 @@ public void shouldThrow400Error() throws Exception { .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"invalid_recipe_id\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -191,7 +191,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser() .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\"}]}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -205,7 +205,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":[],\"passwordHash\":[],\"hashingAlgorithm\":[]}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -219,7 +219,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"$2a\",\"hashingAlgorithm\":\"invalid_algorithm\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -235,7 +235,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser() .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"thirdparty\"}]}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -249,7 +249,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"thirdparty\",\"email\":[],\"thirdPartyId\":[],\"thirdPartyUserId\":[]}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -265,7 +265,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser() .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"passwordless\"}]}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -279,7 +279,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"passwordless\",\"email\":[],\"phoneNumber\":[]}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -296,7 +296,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -313,7 +313,7 @@ public void shouldThrow400Error() throws Exception { "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -329,7 +329,7 @@ public void shouldThrow400Error() throws Exception { JsonObject request = new JsonParser() .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"emailpassword\",\"email\":\"johndoe@gmail.com\",\"passwordHash\":\"$2a\",\"hashingAlgorithm\":\"bcrypt\",\"isPrimary\":true},{\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\",\"isPrimary\":true}]}]}").getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -343,7 +343,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = generateUsersJson(0); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -356,7 +356,7 @@ public void shouldThrow400Error() throws Exception { try { JsonObject request = generateUsersJson(10001); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); @@ -382,7 +382,7 @@ public void shouldReturn200Response() throws Exception { JsonObject request = generateUsersJson(10000); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 10000, null, Utils.getCdiVersionStringLatestForTests(), null); assertEquals("OK", response.get("status").getAsString()); @@ -403,7 +403,7 @@ public void shouldNormaliseFields() throws Exception { JsonObject request = generateUsersJson(1); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); assertEquals("OK", response.get("status").getAsString()); diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java new file mode 100644 index 000000000..0e8ff9fdf --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2024, 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.bulkimport.apis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.List; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; + +import io.supertokens.ProcessState; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; + +import static io.supertokens.test.bulkimport.BulkImportTestUtils.generateBulkImportUser; + +public class DeleteFailedBulkImportUsersTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void shouldReturn400Error() 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; + } + + { + try { + JsonObject request = new JsonObject(); + HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Field name 'ids' is invalid in JSON input", e.getMessage()); + } + } + { + try { + JsonObject request = new JsonParser().parse("{\"ids\":[]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Field name 'ids' cannot be an empty array", e.getMessage()); + } + } + { + try { + JsonObject request = new JsonParser().parse("{\"ids\":[\"\"]}").getAsJsonObject(); + HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Field name 'ids' cannot contain an empty string", e.getMessage()); + } + } + { + try { + // Create a string array of 500 uuids + JsonObject request = new JsonObject(); + JsonArray ids = new JsonArray(); + for (int i = 0; i < 501; i++) { + ids.add(new JsonPrimitive(io.supertokens.utils.Utils.getUUID())); + } + request.add("ids", ids); + + HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Field name 'ids' cannot contain more than 500 elements", e.getMessage()); + } + } + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldReturn200Response() 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; + } + + BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); + + // Insert users + List users = generateBulkImportUser(5); + BulkImport.addUsers(appIdentifierWithStorage, users); + + JsonObject request = new JsonObject(); + JsonArray ids = new JsonArray(); + for (BulkImportUser user : users) { + ids.add(new JsonPrimitive(user.id)); + } + request.add("ids", ids); + + JsonObject response = HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 10000, null, Utils.getCdiVersionStringLatestForTests(), null); + assertEquals("OK", response.get("status").getAsString()); + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java index 64539bee8..77fad9b02 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java @@ -136,7 +136,7 @@ public void shouldReturn200Response() throws Exception { { JsonObject request = new JsonParser().parse(rawData).getAsJsonObject(); JsonObject res = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/add-users", + "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); assert res.get("status").getAsString().equals("OK"); } From ee82e2d2d083396df1882f085316b16b779c91ab Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Mon, 26 Feb 2024 11:30:20 +0530 Subject: [PATCH 08/23] fix: PR changes --- .../bulkimport/BulkImportUserUtils.java | 120 ++++++++++++++---- 1 file changed, 95 insertions(+), 25 deletions(-) diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index e5c107bd8..21349d18c 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -18,7 +18,9 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -79,7 +81,7 @@ private static List getParsedUserRoles(JsonObject userData, List // We already know that the jsonUserRoles is an array of non-empty strings, we will normalise each role now List userRoles = new ArrayList<>(); - jsonUserRoles.forEach(role -> userRoles.add(validateAndNormaliseUserRole(role.getAsString()))); + jsonUserRoles.forEach(role -> userRoles.add(validateAndNormaliseUserRole(role.getAsString(), errors))); return userRoles; } @@ -98,10 +100,10 @@ private static List getParsedTotpDevices(JsonObject userData, List getParsedLoginMethods(Main main, AppIdentifier isPrimary = validateAndNormaliseIsPrimary(isPrimary); isVerified = validateAndNormaliseIsVerified(isVerified); - long timeJoinedInMSSinceEpoch = validateAndNormaliseTimeJoined(timeJoined); + long timeJoinedInMSSinceEpoch = validateAndNormaliseTimeJoined(timeJoined, errors); if ("emailpassword".equals(recipeId)) { String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); String passwordHash = parseAndValidateFieldType(jsonLoginMethodObj, "passwordHash", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); String hashingAlgorithm = parseAndValidateFieldType(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); - email = validateAndNormaliseEmail(email); + email = validateAndNormaliseEmail(email, errors); CoreConfig.PASSWORD_HASHING_ALG normalisedHashingAlgorithm = validateAndNormaliseHashingAlgorithm(hashingAlgorithm, errors); hashingAlgorithm = normalisedHashingAlgorithm != null ? normalisedHashingAlgorithm.toString() : hashingAlgorithm; passwordHash = validateAndNormalisePasswordHash(main, appIdentifier, normalisedHashingAlgorithm, passwordHash, errors); @@ -160,9 +162,9 @@ private static List getParsedLoginMethods(Main main, AppIdentifier String thirdPartyId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); String thirdPartyUserId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyUserId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); - email = validateAndNormaliseEmail(email); - thirdPartyId = validateAndNormaliseThirdPartyId(thirdPartyId); - thirdPartyUserId = validateAndNormaliseThirdPartyUserId(thirdPartyUserId); + email = validateAndNormaliseEmail(email, errors); + thirdPartyId = validateAndNormaliseThirdPartyId(thirdPartyId, errors); + thirdPartyUserId = validateAndNormaliseThirdPartyUserId(thirdPartyUserId, errors); ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, thirdPartyId, thirdPartyUserId); loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, thirdPartyLoginMethod, null)); @@ -170,8 +172,8 @@ private static List getParsedLoginMethods(Main main, AppIdentifier String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); String phoneNumber = parseAndValidateFieldType(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); - email = validateAndNormaliseEmail(email); - phoneNumber = validateAndNormalisePhoneNumber(phoneNumber); + email = validateAndNormaliseEmail(email, errors); + phoneNumber = validateAndNormalisePhoneNumber(phoneNumber, errors); PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, phoneNumber); loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, null, passwordlessLoginMethod)); @@ -180,17 +182,29 @@ private static List getParsedLoginMethods(Main main, AppIdentifier return loginMethods; } - private static String validateAndNormaliseUserRole(String role) { + private static String validateAndNormaliseUserRole(String role, List errors) { + if (role.length() > 255) { + errors.add("role " + role + " is too long. Max length is 255."); + } + // We just trim the role the CreateRoleAPI.java return role.trim(); } - private static String validateAndNormaliseTotpSecretKey(String secretKey) { + private static String validateAndNormaliseTotpSecretKey(String secretKey, List errors) { + if (secretKey == null ) { + return null; + } + + if (secretKey.length() > 256) { + errors.add("TOTP secretKey " + secretKey + " is too long. Max length is 256."); + } + // We don't perform any normalisation on the secretKey in ImportTotpDeviceAPI.java return secretKey; } - private static Integer validateAndNormaliseTotpPeriod(Number period, List errors) { + private static Integer validateAndNormaliseTotpPeriod(Integer period, List errors) { // We don't perform any normalisation on the period in ImportTotpDeviceAPI.java other than checking if it is > 0 if (period != null && period.intValue() < 1) { errors.add("period should be > 0 for a totp device."); @@ -199,7 +213,7 @@ private static Integer validateAndNormaliseTotpPeriod(Number period, List errors) { + private static Integer validateAndNormaliseTotpSkew(Integer skew, List errors) { // We don't perform any normalisation on the period in ImportTotpDeviceAPI.java other than checking if it is >= 0 if (skew != null && skew.intValue() < 0) { errors.add("skew should be >= 0 for a totp device."); @@ -208,9 +222,17 @@ private static Integer validateAndNormaliseTotpSkew(Number skew, List er return skew != null ? skew.intValue() : null; } - private static String validateAndNormaliseTotpDeviceName(String deviceName) { + private static String validateAndNormaliseTotpDeviceName(String deviceName, List errors) { + if (deviceName == null ) { + return null; + } + + if (deviceName.length() > 256) { + errors.add("TOTP deviceName " + deviceName + " is too long. Max length is 256."); + } + // We normalise the deviceName as per the ImportTotpDeviceAPI.java - return deviceName != null ? deviceName.trim() : null; + return deviceName.trim(); } private static void validateAndNormaliseIsPrimaryField(JsonArray jsonLoginMethods, List errors) { @@ -257,7 +279,7 @@ private static String validateAndNormaliseTenantId(Main main, AppIdentifier appI // We make the tenantId lowercase while parsing from the request in WebserverAPI.java String normalisedTenantId = tenantId.trim().toLowerCase(); TenantConfig[] allTenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main); - ArrayList validTenantIds = new ArrayList<>(); + Set validTenantIds = new HashSet<>(); Arrays.stream(allTenantConfigs) .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); @@ -277,14 +299,34 @@ private static Boolean validateAndNormaliseIsVerified(Boolean isVerified) { return isVerified == null ? false : isVerified; } - private static long validateAndNormaliseTimeJoined(Integer timeJoined) { + private static long validateAndNormaliseTimeJoined(Integer timeJoined, List errors) { // We default timeJoined to 0 if it is null - return timeJoined != null ? timeJoined.longValue() : 0; + if (timeJoined == null) { + return 0; + } + + if (timeJoined > System.currentTimeMillis()) { + errors.add("timeJoined cannot be in future for a loginMethod."); + } + + if (timeJoined < 0) { + errors.add("timeJoined cannot be < 0 for a loginMethod."); + } + + return timeJoined.longValue(); } - private static String validateAndNormaliseEmail(String email) { + private static String validateAndNormaliseEmail(String email, List errors) { + if (email == null) { + return null; + } + + if (email.length() > 255) { + errors.add("email " + email + " is too long. Max length is 256."); + } + // We normalise the email as per the SignUpAPI.java - return email != null ? Utils.normaliseEmail(email) : null; + return Utils.normaliseEmail(email); } private static CoreConfig.PASSWORD_HASHING_ALG validateAndNormaliseHashingAlgorithm(String hashingAlgorithm, List errors) { @@ -305,6 +347,10 @@ private static String validateAndNormalisePasswordHash(Main main, AppIdentifier if (hashingAlgorithm == null || passwordHash == null) { return passwordHash; } + + if (passwordHash.length() > 256) { + errors.add("passwordHash is too long. Max length is 256."); + } // We trim the passwordHash and validate it as per ImportUserWithPasswordHashAPI.java passwordHash = passwordHash.trim(); @@ -318,19 +364,43 @@ private static String validateAndNormalisePasswordHash(Main main, AppIdentifier return passwordHash; } - private static String validateAndNormaliseThirdPartyId(String thirdPartyId) { + private static String validateAndNormaliseThirdPartyId(String thirdPartyId, List errors) { + if (thirdPartyId == null) { + return null; + } + + if (thirdPartyId.length() > 28) { + errors.add("thirdPartyId " + thirdPartyId + " is too long. Max length is 28."); + } + // We don't perform any normalisation on the thirdPartyId in SignInUpAPI.java return thirdPartyId; } - private static String validateAndNormaliseThirdPartyUserId(String thirdPartyUserId) { + private static String validateAndNormaliseThirdPartyUserId(String thirdPartyUserId, List errors) { + if (thirdPartyUserId == null) { + return null; + } + + if (thirdPartyUserId.length() > 256) { + errors.add("thirdPartyUserId " + thirdPartyUserId + " is too long. Max length is 256."); + } + // We don't perform any normalisation on the thirdPartyUserId in SignInUpAPI.java return thirdPartyUserId; } - private static String validateAndNormalisePhoneNumber(String phoneNumber) { + private static String validateAndNormalisePhoneNumber(String phoneNumber, List errors) { + if (phoneNumber == null) { + return null; + } + + if (phoneNumber.length() > 256) { + errors.add("phoneNumber " + phoneNumber + " is too long. Max length is 256."); + } + // We normalise the phoneNumber as per the CreateCodeAPI.java - return phoneNumber != null ? Utils.normalizeIfPhoneNumber(phoneNumber) : null; + return Utils.normalizeIfPhoneNumber(phoneNumber); } } From 39821968017d23fcac17c0507acccb0c6741378e Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Tue, 27 Feb 2024 14:59:52 +0530 Subject: [PATCH 09/23] fix: PR changes --- .../java/io/supertokens/inmemorydb/config/SQLiteConfig.java | 4 ---- .../io/supertokens/inmemorydb/queries/GeneralQueries.java | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java b/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java index e27c3ea16..ee8c43241 100644 --- a/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java +++ b/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java @@ -164,8 +164,4 @@ public String getDashboardUsersTable() { public String getDashboardSessionsTable() { return "dashboard_user_sessions"; } - - public String getBulkImportUsersTable() { - return "bulk_import_users"; - } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 0f519dabf..46fc162af 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -416,6 +416,7 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc // index: update(start, TOTPQueries.getQueryToCreateUsedCodesExpiryTimeIndex(start), NO_OP_SETTER); } + } From c02ad5403bf2fafa04051b906525c58cbe17fc1a Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Tue, 27 Feb 2024 16:11:54 +0530 Subject: [PATCH 10/23] fix: PR changes --- .../io/supertokens/bulkimport/BulkImport.java | 6 +- .../api/bulkimport/BulkImportAPI.java | 57 +++++++++++++++++++ .../apis/DeleteFailedBulkImportUsersTest.java | 20 +++++-- 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index b3fc8df17..09444d085 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -35,7 +35,7 @@ public class BulkImport { public static final int MAX_USERS_TO_ADD = 10000; public static final int GET_USERS_PAGINATION_LIMIT = 500; public static final int GET_USERS_DEFAULT_LIMIT = 100; - public static final int DELETE_FAILED_USERS_LIMIT = 500; + public static final int DELETE_USERS_LIMIT = 500; public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, List users) throws StorageQueryException, TenantOrAppNotFoundException { @@ -78,7 +78,7 @@ public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorag return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken); } - public static void deleteUsers(AppIdentifierWithStorage appIdentifierWithStorage, String[] userIds) throws StorageQueryException { - appIdentifierWithStorage.getBulkImportStorage().deleteBulkImportUsers(appIdentifierWithStorage, userIds); + public static List deleteUsers(AppIdentifierWithStorage appIdentifierWithStorage, String[] userIds) throws StorageQueryException { + return appIdentifierWithStorage.getBulkImportStorage().deleteBulkImportUsers(appIdentifierWithStorage, userIds); } } diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java index 9c6d64310..fd611373f 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.List; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -172,4 +173,60 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); } + + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + JsonArray arr = InputParser.parseArrayOrThrowError(input, "ids", false); + + if (arr.size() == 0) { + throw new ServletException(new WebserverAPI.BadRequestException("Field name 'ids' cannot be an empty array")); + } + + if (arr.size() > BulkImport.DELETE_USERS_LIMIT) { + throw new ServletException(new WebserverAPI.BadRequestException("Field name 'ids' cannot contain more than " + + BulkImport.DELETE_USERS_LIMIT + " elements")); + } + + String[] userIds = new String[arr.size()]; + + for (int i = 0; i < userIds.length; i++) { + String userId = InputParser.parseStringFromElementOrThrowError(arr.get(i), "ids", false); + if (userId.isEmpty()) { + throw new ServletException(new WebserverAPI.BadRequestException("Field name 'ids' cannot contain an empty string")); + } + userIds[i] = userId; + } + + AppIdentifierWithStorage appIdentifierWithStorage; + try { + appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } + + try { + List deletedUserIds = BulkImport.deleteUsers(appIdentifierWithStorage, userIds); + + JsonArray deletedUserIdsJson = new JsonArray(); + JsonArray invalidUserIds = new JsonArray(); + + for (String userId : userIds) { + if (deletedUserIds.contains(userId)) { + deletedUserIdsJson.add(new JsonPrimitive(userId)); + } else { + invalidUserIds.add(new JsonPrimitive(userId)); + } + } + + JsonObject result = new JsonObject(); + result.add("deletedUserIds", deletedUserIdsJson); + result.add("invalidUserIds", invalidUserIds); + + super.sendJsonResponse(200, result, resp); + + } catch (StorageQueryException e) { + throw new ServletException(e); + } + } } diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java index 0e8ff9fdf..4ac29e8ac 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.util.List; @@ -145,17 +146,26 @@ public void shouldReturn200Response() throws Exception { List users = generateBulkImportUser(5); BulkImport.addUsers(appIdentifierWithStorage, users); + String invalidId = io.supertokens.utils.Utils.getUUID(); JsonObject request = new JsonObject(); - JsonArray ids = new JsonArray(); + JsonArray validIds = new JsonArray(); for (BulkImportUser user : users) { - ids.add(new JsonPrimitive(user.id)); + validIds.add(new JsonPrimitive(user.id)); } - request.add("ids", ids); + validIds.add(new JsonPrimitive(invalidId)); + + request.add("ids", validIds); + JsonObject response = HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", - request, 1000, 10000, null, Utils.getCdiVersionStringLatestForTests(), null); - assertEquals("OK", response.get("status").getAsString()); + request, 1000000, 1000000, null, Utils.getCdiVersionStringLatestForTests(), null); + + response.get("deletedUserIds").getAsJsonArray().forEach(id -> { + assertTrue(validIds.contains(id)); + }); + + assertEquals(invalidId, response.get("invalidUserIds").getAsJsonArray().get(0).getAsString()); process.kill(); Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); From 7d25645c1ab25454a70414263311b6704c0271fe Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Tue, 27 Feb 2024 16:52:35 +0530 Subject: [PATCH 11/23] fix: PR changes --- .../bulkimport/BulkImportUserUtils.java | 17 ++++++++++++++++- .../supertokens/utils/JsonValidatorUtils.java | 4 ++-- .../test/bulkimport/BulkImportTestUtils.java | 5 +++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index 21349d18c..083d280f1 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -64,6 +64,8 @@ public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifi List totpDevices = getParsedTotpDevices(userData, errors); List loginMethods = getParsedLoginMethods(main, appIdentifier, userData, errors); + externalUserId = validateAndNormaliseExternalUserId(externalUserId, errors); + if (!errors.isEmpty()) { throw new InvalidBulkImportDataException(errors); } @@ -182,12 +184,25 @@ private static List getParsedLoginMethods(Main main, AppIdentifier return loginMethods; } + private static String validateAndNormaliseExternalUserId(String externalUserId, List errors) { + if (externalUserId == null ) { + return null; + } + + if (externalUserId.length() > 255) { + errors.add("externalUserId " + externalUserId + " is too long. Max length is 128."); + } + + // We just trim the externalUserId as per the UpdateExternalUserIdInfoAPI.java + return externalUserId.trim(); + } + private static String validateAndNormaliseUserRole(String role, List errors) { if (role.length() > 255) { errors.add("role " + role + " is too long. Max length is 255."); } - // We just trim the role the CreateRoleAPI.java + // We just trim the role as per the CreateRoleAPI.java return role.trim(); } diff --git a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java index beb65a87b..7fdc564fa 100644 --- a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java +++ b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java @@ -90,7 +90,7 @@ public static boolean validateJsonFieldType(JsonObject jsonObject, String key, V if (jsonObject.has(key)) { return switch (expectedType) { case STRING -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isString() - && !jsonObject.get(key).getAsString().isEmpty(); + && !jsonObject.get(key).getAsString().isBlank(); case INTEGER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); case BOOLEAN -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isBoolean(); case OBJECT -> jsonObject.get(key).isJsonObject(); @@ -110,7 +110,7 @@ public static boolean validateArrayElements(JsonArray array, ValueType expectedT case ARRAY_OF_OBJECT -> elements.stream().allMatch(JsonElement::isJsonObject); case ARRAY_OF_STRING -> elements.stream().allMatch(el -> el.isJsonPrimitive() && el.getAsJsonPrimitive().isString() - && !el.getAsString().isEmpty()); + && !el.getAsString().isBlank()); default -> false; }; } diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java index 86952977f..61e9c6241 100644 --- a/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java @@ -21,6 +21,7 @@ import java.util.List; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; @@ -32,14 +33,14 @@ public class BulkImportTestUtils { public static List generateBulkImportUser(int numberOfUsers) { List users = new ArrayList<>(); + JsonParser parser = new JsonParser(); for (int i = 0; i < numberOfUsers; i++) { String email = "user" + i + "@example.com"; String id = io.supertokens.utils.Utils.getUUID(); String externalId = io.supertokens.utils.Utils.getUUID(); - JsonObject userMetadata = new JsonObject(); - userMetadata.addProperty("key", "value"); + JsonObject userMetadata = parser.parse("{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}").getAsJsonObject(); List userRoles = new ArrayList<>(); userRoles.add("role1"); From 3bb6adfdc651bacf5d87bcc2b416aa6ee482e916 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 10:16:27 +0530 Subject: [PATCH 12/23] fix: PR changes --- .../io/supertokens/bulkimport/BulkImport.java | 7 ++-- .../BulkImportUserPaginationContainer.java | 6 ++-- .../bulkimport/BulkImportUserUtils.java | 35 +++++++++--------- .../supertokens/utils/JsonValidatorUtils.java | 8 ++++- .../api/bulkimport/BulkImportAPI.java | 17 +++++---- .../test/bulkimport/BulkImportTest.java | 31 ++++++++-------- .../test/bulkimport/BulkImportTestUtils.java | 15 ++------ .../apis/AddBulkImportUsersTest.java | 36 ++++++++++++++++--- 8 files changed, 92 insertions(+), 63 deletions(-) diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index 49e539bfc..f2136064d 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -18,7 +18,6 @@ import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; -import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -54,7 +53,7 @@ public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, L public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorage appIdentifierWithStorage, @Nonnull Integer limit, @Nullable BulkImportUserStatus status, @Nullable String paginationToken) throws StorageQueryException, BulkImportUserPaginationToken.InvalidTokenException { - List users; + List users; if (paginationToken == null) { users = appIdentifierWithStorage.getBulkImportStorage() @@ -69,11 +68,11 @@ public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorag int maxLoop = users.size(); if (users.size() == limit + 1) { maxLoop = limit; - BulkImportUserInfo user = users.get(limit); + BulkImportUser user = users.get(limit); nextPaginationToken = new BulkImportUserPaginationToken(user.id, user.createdAt).generateToken(); } - List resultUsers = users.subList(0, maxLoop); + List resultUsers = users.subList(0, maxLoop); return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken); } } diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java index 2993a83a7..f691c68c3 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java @@ -21,13 +21,13 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; public class BulkImportUserPaginationContainer { - public final List users; + public final List users; public final String nextPaginationToken; - public BulkImportUserPaginationContainer(@Nonnull List users, @Nullable String nextPaginationToken) { + public BulkImportUserPaginationContainer(@Nonnull List users, @Nullable String nextPaginationToken) { this.users = users; this.nextPaginationToken = nextPaginationToken; } diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index 083d280f1..7b1f20a3c 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -36,9 +36,6 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; -import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.EmailPasswordLoginMethod; -import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.ThirdPartyLoginMethod; -import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.PasswordlessLoginMethod; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -52,7 +49,7 @@ import static io.supertokens.utils.JsonValidatorUtils.validateJsonFieldType; public class BulkImportUserUtils { - public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifier appIdentifier, JsonObject userData, String id) + public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifier appIdentifier, JsonObject userData, String id, String[] allUserRoles) throws InvalidBulkImportDataException, StorageQueryException, TenantOrAppNotFoundException { List errors = new ArrayList<>(); @@ -60,7 +57,7 @@ public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifi errors, "."); JsonObject userMetadata = parseAndValidateFieldType(userData, "userMetadata", ValueType.OBJECT, false, JsonObject.class, errors, "."); - List userRoles = getParsedUserRoles(userData, errors); + List userRoles = getParsedUserRoles(userData, allUserRoles, errors); List totpDevices = getParsedTotpDevices(userData, errors); List loginMethods = getParsedLoginMethods(main, appIdentifier, userData, errors); @@ -72,7 +69,7 @@ public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifi return new BulkImportUser(id, externalUserId, userMetadata, userRoles, totpDevices, loginMethods); } - private static List getParsedUserRoles(JsonObject userData, List errors) { + private static List getParsedUserRoles(JsonObject userData, String[] allUserRoles, List errors) { JsonArray jsonUserRoles = parseAndValidateFieldType(userData, "roles", ValueType.ARRAY_OF_STRING, false, JsonArray.class, errors, "."); @@ -83,7 +80,14 @@ private static List getParsedUserRoles(JsonObject userData, List // We already know that the jsonUserRoles is an array of non-empty strings, we will normalise each role now List userRoles = new ArrayList<>(); - jsonUserRoles.forEach(role -> userRoles.add(validateAndNormaliseUserRole(role.getAsString(), errors))); + jsonUserRoles.forEach(role -> { + String normalisedRole = validateAndNormaliseUserRole(role.getAsString(), errors); + if (Arrays.asList(allUserRoles).contains(normalisedRole)) { + userRoles.add(normalisedRole); + } else { + errors.add("Role " + normalisedRole + " does not exist."); + } + }); return userRoles; } @@ -138,7 +142,7 @@ private static List getParsedLoginMethods(Main main, AppIdentifier String tenantId = parseAndValidateFieldType(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod."); Boolean isVerified = parseAndValidateFieldType(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); Boolean isPrimary = parseAndValidateFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); - Integer timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.INTEGER, false, Integer.class, errors, " for a loginMethod"); + Long timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.LONG, false, Long.class, errors, " for a loginMethod"); recipeId = validateAndNormaliseRecipeId(recipeId, errors); tenantId= validateAndNormaliseTenantId(main, appIdentifier, tenantId, recipeId, errors); @@ -157,8 +161,7 @@ private static List getParsedLoginMethods(Main main, AppIdentifier hashingAlgorithm = normalisedHashingAlgorithm != null ? normalisedHashingAlgorithm.toString() : hashingAlgorithm; passwordHash = validateAndNormalisePasswordHash(main, appIdentifier, normalisedHashingAlgorithm, passwordHash, errors); - EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, passwordHash, hashingAlgorithm); - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, emailPasswordLoginMethod, null, null)); + loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, email, passwordHash, hashingAlgorithm, null, null, null)); } else if ("thirdparty".equals(recipeId)) { String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); String thirdPartyId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); @@ -168,8 +171,7 @@ private static List getParsedLoginMethods(Main main, AppIdentifier thirdPartyId = validateAndNormaliseThirdPartyId(thirdPartyId, errors); thirdPartyUserId = validateAndNormaliseThirdPartyUserId(thirdPartyUserId, errors); - ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, thirdPartyId, thirdPartyUserId); - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, thirdPartyLoginMethod, null)); + loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, email, null, null, thirdPartyId, thirdPartyUserId, null)); } else if ("passwordless".equals(recipeId)) { String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); String phoneNumber = parseAndValidateFieldType(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); @@ -177,8 +179,7 @@ private static List getParsedLoginMethods(Main main, AppIdentifier email = validateAndNormaliseEmail(email, errors); phoneNumber = validateAndNormalisePhoneNumber(phoneNumber, errors); - PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, phoneNumber); - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, null, null, passwordlessLoginMethod)); + loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, email, null, null, null, null, phoneNumber)); } } return loginMethods; @@ -314,10 +315,10 @@ private static Boolean validateAndNormaliseIsVerified(Boolean isVerified) { return isVerified == null ? false : isVerified; } - private static long validateAndNormaliseTimeJoined(Integer timeJoined, List errors) { - // We default timeJoined to 0 if it is null + private static long validateAndNormaliseTimeJoined(Long timeJoined, List errors) { + // We default timeJoined to currentTime if it is null if (timeJoined == null) { - return 0; + return System.currentTimeMillis(); } if (timeJoined > System.currentTimeMillis()) { diff --git a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java index 7fdc564fa..6c2f81cf3 100644 --- a/src/main/java/io/supertokens/utils/JsonValidatorUtils.java +++ b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java @@ -38,6 +38,10 @@ public static T parseAndValidateFieldType(JsonObject jsonObject, String key, Integer intValue = jsonObject.get(key).getAsNumber().intValue(); value = (T) intValue; break; + case LONG: + Long longValue = jsonObject.get(key).getAsNumber().longValue(); + value = (T) longValue; + break; case BOOLEAN: Boolean boolValue = jsonObject.get(key).getAsBoolean(); value = (T) boolValue; @@ -69,6 +73,7 @@ public static T parseAndValidateFieldType(JsonObject jsonObject, String key, public enum ValueType { STRING, INTEGER, + LONG, BOOLEAN, OBJECT, ARRAY_OF_STRING, @@ -79,6 +84,7 @@ private static String getTypeForErrorMessage(ValueType type) { return switch (type) { case STRING -> "string"; case INTEGER -> "integer"; + case LONG -> "integer"; // choosing integer over long because it is user facing case BOOLEAN -> "boolean"; case OBJECT -> "object"; case ARRAY_OF_STRING -> "array of string"; @@ -91,7 +97,7 @@ public static boolean validateJsonFieldType(JsonObject jsonObject, String key, V return switch (expectedType) { case STRING -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isString() && !jsonObject.get(key).getAsString().isBlank(); - case INTEGER -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); + case INTEGER, LONG -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isNumber(); case BOOLEAN -> jsonObject.get(key).isJsonPrimitive() && jsonObject.getAsJsonPrimitive(key).isBoolean(); case OBJECT -> jsonObject.get(key).isJsonObject(); case ARRAY_OF_OBJECT, ARRAY_OF_STRING -> jsonObject.get(key).isJsonArray() diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java index 9c6d64310..2f1f5f94a 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java @@ -32,7 +32,6 @@ import io.supertokens.output.Logging; import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; -import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -93,7 +92,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se result.addProperty("status", "OK"); JsonArray usersJson = new JsonArray(); - for (BulkImportUserInfo user : users.users) { + for (BulkImportUser user : users.users) { usersJson.add(user.toJsonObject()); } result.add("users", usersJson); @@ -130,12 +129,20 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S throw new ServletException(e); } + String[] allUserRoles = null; + + try { + allUserRoles = appIdentifierWithStorage.getUserRolesStorage().getRoles(appIdentifierWithStorage); + } catch (StorageQueryException e) { + throw new ServletException(e); + } + JsonArray errorsJson = new JsonArray(); ArrayList usersToAdd = new ArrayList<>(); for (int i = 0; i < users.size(); i++) { try { - BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifierWithStorage, users.get(i).getAsJsonObject(), Utils.getUUID()); + BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifierWithStorage, users.get(i).getAsJsonObject(), Utils.getUUID(), allUserRoles); usersToAdd.add(user); } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { JsonObject errorObj = new JsonObject(); @@ -147,9 +154,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S errorObj.addProperty("index", i); errorObj.add("errors", errors); errorsJson.add(errorObj); - } catch (Exception e) { - System.out.println("error: " + e.getMessage()); - e.printStackTrace(); + } catch (StorageQueryException | TenantOrAppNotFoundException e) { throw new ServletException(e); } } diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java index 6d9b327c5..4ff949e09 100644 --- a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java @@ -37,7 +37,6 @@ import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; -import io.supertokens.pluginInterface.bulkimport.BulkImportUserInfo; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.storageLayer.StorageLayer; @@ -76,18 +75,18 @@ public void shouldAddUsersInBulkImportUsersTable() throws Exception { BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); BulkImport.addUsers(new AppIdentifierWithStorage(null, null, storage), users); - List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, BulkImportUserStatus.NEW, null, null); + List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, BulkImportUserStatus.NEW, null, null); // Verify that all users are present in addedUsers for (BulkImportUser user : users) { - BulkImportUserInfo matchingUser = addedUsers.stream() + BulkImportUser matchingUser = addedUsers.stream() .filter(addedUser -> user.id.equals(addedUser.id)) .findFirst() .orElse(null); assertNotNull(matchingUser); assertEquals(BulkImportUserStatus.NEW, matchingUser.status); - assertEquals(user.toString(), matchingUser.rawData); + assertEquals(user.toString(), matchingUser.toRawData()); } process.kill(); @@ -116,12 +115,12 @@ public void shouldCreatedNewIdsIfDuplicateIdIsFound() throws Exception { AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); BulkImport.addUsers(appIdentifierWithStorage, users); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); // Verify that the other properties are same but ids changed for (BulkImportUser user : users) { - BulkImportUserInfo matchingUser = addedUsers.stream() - .filter(addedUser -> user.toString().equals(addedUser.rawData)) + BulkImportUser matchingUser = addedUsers.stream() + .filter(addedUser -> user.toString().equals(addedUser.toRawData())) .findFirst() .orElse(null); @@ -153,7 +152,7 @@ public void testGetUsersStatusFilter() throws Exception { List users = generateBulkImportUser(10); BulkImport.addUsers(appIdentifierWithStorage, users); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); assertEquals(10, addedUsers.size()); } @@ -171,7 +170,7 @@ public void testGetUsersStatusFilter() throws Exception { return null; }); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.PROCESSING, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.PROCESSING, null, null); assertEquals(10, addedUsers.size()); } @@ -189,7 +188,7 @@ public void testGetUsersStatusFilter() throws Exception { return null; }); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.FAILED, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.FAILED, null, null); assertEquals(10, addedUsers.size()); } @@ -223,13 +222,13 @@ public void randomPaginationTest() throws Exception { } // Get all inserted users - List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, null, null, null); + List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, null, null, null); assertEquals(numberOfUsers, addedUsers.size()); // We are sorting the users based on createdAt and id like we do in the storage layer - List sortedUsers = addedUsers.stream() + List sortedUsers = addedUsers.stream() .sorted((user1, user2) -> { - int compareResult = user2.createdAt.compareTo(user1.createdAt); + int compareResult = Long.compare(user2.createdAt, user1.createdAt); if (compareResult == 0) { return user2.id.compareTo(user1.id); } @@ -245,11 +244,11 @@ public void randomPaginationTest() throws Exception { do { BulkImportUserPaginationContainer users = BulkImport.getUsers(new AppIdentifierWithStorage(null, null, storage), limit, null, paginationToken); - for (BulkImportUserInfo actualUser : users.users) { - BulkImportUserInfo expectedUser = sortedUsers.get(indexIntoUsers); + for (BulkImportUser actualUser : users.users) { + BulkImportUser expectedUser = sortedUsers.get(indexIntoUsers); assertEquals(expectedUser.id, actualUser.id); - assertEquals(expectedUser.rawData, actualUser.rawData); + assertEquals(expectedUser.toString(), actualUser.toString()); indexIntoUsers++; } diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java index 61e9c6241..1aecc66c6 100644 --- a/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java @@ -25,9 +25,6 @@ import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; -import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.EmailPasswordLoginMethod; -import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.PasswordlessLoginMethod; -import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod.ThirdPartyLoginMethod; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; public class BulkImportTestUtils { @@ -43,20 +40,14 @@ public static List generateBulkImportUser(int numberOfUsers) { JsonObject userMetadata = parser.parse("{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}").getAsJsonObject(); List userRoles = new ArrayList<>(); - userRoles.add("role1"); - userRoles.add("role2"); List totpDevices = new ArrayList<>(); totpDevices.add(new TotpDevice("secretKey", 30, 1, "deviceName")); - EmailPasswordLoginMethod emailPasswordLoginMethod = new EmailPasswordLoginMethod(email, "$2a", "BCRYPT"); - ThirdPartyLoginMethod thirdPartyLoginMethod = new ThirdPartyLoginMethod(email, "thirdPartyId", "thirdPartyUserId"); - PasswordlessLoginMethod passwordlessLoginMethod = new PasswordlessLoginMethod(email, "+911234567890"); - List loginMethods = new ArrayList<>(); - loginMethods.add(new LoginMethod("public", "emailpassword", true, true, 0, emailPasswordLoginMethod, null, null)); - loginMethods.add(new LoginMethod("public", "thirdparty", true, false, 0, null, thirdPartyLoginMethod, null)); - loginMethods.add(new LoginMethod("public", "passwordless", true, false, 0, null, null, passwordlessLoginMethod)); + loginMethods.add(new LoginMethod("public", "emailpassword", true, true, 0, email, "$2a", "BCRYPT", null, null, null)); + loginMethods.add(new LoginMethod("public", "thirdparty", true, false, 0, email, null, null, "thirdPartyId", "thirdPartyUserId", null)); + loginMethods.add(new LoginMethod("public", "passwordless", true, false, 0, email, null, null, null, null, "+911234567890")); users.add(new BulkImportUser(id, externalId, userMetadata, userRoles, totpDevices, loginMethods)); } return users; diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index 376f4b3ff..4684b0f92 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -41,6 +41,7 @@ import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.userroles.UserRoles; public class AddBulkImportUsersTest { @Rule @@ -151,6 +152,21 @@ public void shouldThrow400Error() throws Exception { assertEquals(responseString, "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"externalUserId should be of type string.\",\"roles should be of type array of string.\",\"totp should be of type array of object.\",\"loginMethods is required.\"]}]}"); } + // Invalid role (does not exist) + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"roles\":[\"role5\"]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Role role5 does not exist.\",\"loginMethods is required.\"]}]}"); + } } // Invalid field type of non required fields inside loginMethod { @@ -380,6 +396,12 @@ public void shouldReturn200Response() throws Exception { return; } + // Create user roles before inserting bulk users + { + UserRoles.createNewRoleOrModifyItsPermissions(process.getProcess(), "role1", null); + UserRoles.createNewRoleOrModifyItsPermissions(process.getProcess(), "role2", null); + } + JsonObject request = generateUsersJson(10000); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", @@ -401,6 +423,12 @@ public void shouldNormaliseFields() throws Exception { return; } + // Create user roles before inserting bulk users + { + UserRoles.createNewRoleOrModifyItsPermissions(process.getProcess(), "role1", null); + UserRoles.createNewRoleOrModifyItsPermissions(process.getProcess(), "role2", null); + } + JsonObject request = generateUsersJson(1); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", @@ -422,10 +450,10 @@ public void shouldNormaliseFields() throws Exception { for (int i = 0; i < loginMethods.size(); i++) { JsonObject loginMethod = loginMethods.get(i).getAsJsonObject(); if (loginMethod.has("email")) { - assertEquals("johndoe+1@gmail.com", loginMethod.get("hashingAlgorithm").getAsString()); + assertEquals("johndoe+0@gmail.com", loginMethod.get("email").getAsString()); } if (loginMethod.has("phoneNumber")) { - assertEquals("919999999999", loginMethod.get("phoneNumber").getAsString()); + assertEquals("+919999999999", loginMethod.get("phoneNumber").getAsString()); } if (loginMethod.has("hashingAlgorithm")) { assertEquals("ARGON2", loginMethod.get("hashingAlgorithm").getAsString()); @@ -446,8 +474,8 @@ public static JsonObject generateUsersJson(int numberOfUsers) { user.addProperty("externalUserId", UUID.randomUUID().toString()); user.add("userMetadata", parser.parse("{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}")); - user.add("roles", parser.parse("[\"role1\", \"role2\"]")); - user.add("totp", parser.parse("[{\"secretKey\":\"secretKey\",\"period\": 30,\"skew\":1,\"deviceName\":\"deviceName\"}]")); + user.add("userRoles", parser.parse("[\"role1\", \"role2\"]")); + user.add("totpDevices", parser.parse("[{\"secretKey\":\"secretKey\",\"period\": 30,\"skew\":1,\"deviceName\":\"deviceName\"}]")); String email = " johndoe+" + i + "@gmail.com "; From 928563235f1af008b0cbce870cb9169565e0f356 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 10:46:41 +0530 Subject: [PATCH 13/23] fix: PR changes --- .../test/bulkimport/apis/AddBulkImportUsersTest.java | 3 +-- .../test/bulkimport/apis/GetBulkImportUsersTest.java | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index 4684b0f92..94ce885b0 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -443,9 +443,8 @@ public void shouldNormaliseFields() throws Exception { JsonArray bulkImportUsers = getResponse.get("users").getAsJsonArray(); assertEquals(1, bulkImportUsers.size()); - JsonParser parser = new JsonParser(); JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); - JsonArray loginMethods = parser.parse(bulkImportUserJson.get("rawData").getAsString()).getAsJsonObject().getAsJsonArray("loginMethods"); + JsonArray loginMethods = bulkImportUserJson.getAsJsonArray("loginMethods"); for (int i = 0; i < loginMethods.size(); i++) { JsonObject loginMethod = loginMethods.get(i).getAsJsonObject(); diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java index 77fad9b02..5efb99a58 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java @@ -35,6 +35,7 @@ import io.supertokens.ProcessState; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -150,7 +151,7 @@ public void shouldReturn200Response() throws Exception { assertEquals(1, bulkImportUsers.size()); JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); bulkImportUserJson.get("status").getAsString().equals("NEW"); - bulkImportUserJson.get("rawData").getAsString().equals(rawData); + BulkImportUser.fromJson(bulkImportUserJson).toRawData().equals(rawData); process.kill(); Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); From f09cf721d5aa2b8f3c3cd08bd5d78e7b2826e881 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 12:33:56 +0530 Subject: [PATCH 14/23] fix: PR changes --- .../io/supertokens/bulkimport/BulkImport.java | 4 +-- .../bulkimport/BulkImportUserUtils.java | 19 +++++++------ .../api/bulkimport/BulkImportAPI.java | 6 ++--- .../test/bulkimport/BulkImportTest.java | 27 ++++++++++--------- .../apis/GetBulkImportUsersTest.java | 2 +- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index f2136064d..1f9e38d06 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -16,7 +16,7 @@ package io.supertokens.bulkimport; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -51,7 +51,7 @@ public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, L } public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorage appIdentifierWithStorage, - @Nonnull Integer limit, @Nullable BulkImportUserStatus status, @Nullable String paginationToken) + @Nonnull Integer limit, @Nullable BULK_IMPORT_USER_STATUS status, @Nullable String paginationToken) throws StorageQueryException, BulkImportUserPaginationToken.InvalidTokenException { List users; diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index 7b1f20a3c..2d3f0003d 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -80,14 +80,7 @@ private static List getParsedUserRoles(JsonObject userData, String[] all // We already know that the jsonUserRoles is an array of non-empty strings, we will normalise each role now List userRoles = new ArrayList<>(); - jsonUserRoles.forEach(role -> { - String normalisedRole = validateAndNormaliseUserRole(role.getAsString(), errors); - if (Arrays.asList(allUserRoles).contains(normalisedRole)) { - userRoles.add(normalisedRole); - } else { - errors.add("Role " + normalisedRole + " does not exist."); - } - }); + jsonUserRoles.forEach(role -> validateAndNormaliseUserRole(role.getAsString(), allUserRoles, errors)); return userRoles; } @@ -198,13 +191,19 @@ private static String validateAndNormaliseExternalUserId(String externalUserId, return externalUserId.trim(); } - private static String validateAndNormaliseUserRole(String role, List errors) { + private static String validateAndNormaliseUserRole(String role, String[] allUserRoles, List errors) { if (role.length() > 255) { errors.add("role " + role + " is too long. Max length is 255."); } // We just trim the role as per the CreateRoleAPI.java - return role.trim(); + String normalisedRole = role.trim(); + + if (!Arrays.asList(allUserRoles).contains(normalisedRole)) { + errors.add("Role " + normalisedRole + " does not exist."); + } + + return normalisedRole; } private static String validateAndNormaliseTotpSecretKey(String secretKey, List errors) { diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java index 2f1f5f94a..d6e903bb9 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java @@ -30,7 +30,7 @@ import io.supertokens.bulkimport.BulkImportUserUtils; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -69,10 +69,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se limit = BulkImport.GET_USERS_DEFAULT_LIMIT; } - BulkImportUserStatus status = null; + BULK_IMPORT_USER_STATUS status = null; if (statusString != null) { try { - status = BulkImportUserStatus.valueOf(statusString); + status = BULK_IMPORT_USER_STATUS.valueOf(statusString); } catch (IllegalArgumentException e) { throw new ServletException(new BadRequestException("Invalid value for status. Pass one of NEW, PROCESSING, or FAILED!")); } diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java index 4ff949e09..dba270c3b 100644 --- a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java @@ -35,7 +35,7 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BulkImportUserStatus; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -75,7 +75,7 @@ public void shouldAddUsersInBulkImportUsersTable() throws Exception { BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); BulkImport.addUsers(new AppIdentifierWithStorage(null, null, storage), users); - List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, BulkImportUserStatus.NEW, null, null); + List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, BULK_IMPORT_USER_STATUS.NEW, null, null); // Verify that all users are present in addedUsers for (BulkImportUser user : users) { @@ -85,8 +85,8 @@ public void shouldAddUsersInBulkImportUsersTable() throws Exception { .orElse(null); assertNotNull(matchingUser); - assertEquals(BulkImportUserStatus.NEW, matchingUser.status); - assertEquals(user.toString(), matchingUser.toRawData()); + assertEquals(BULK_IMPORT_USER_STATUS.NEW, matchingUser.status); + assertEquals(user.toRawDataForDbStorage(), matchingUser.toRawDataForDbStorage()); } process.kill(); @@ -115,17 +115,17 @@ public void shouldCreatedNewIdsIfDuplicateIdIsFound() throws Exception { AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); BulkImport.addUsers(appIdentifierWithStorage, users); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BULK_IMPORT_USER_STATUS.NEW, null, null); // Verify that the other properties are same but ids changed for (BulkImportUser user : users) { BulkImportUser matchingUser = addedUsers.stream() - .filter(addedUser -> user.toString().equals(addedUser.toRawData())) + .filter(addedUser -> user.toRawDataForDbStorage().equals(addedUser.toRawDataForDbStorage())) .findFirst() .orElse(null); assertNotNull(matchingUser); - assertEquals(BulkImportUserStatus.NEW, matchingUser.status); + assertEquals(BULK_IMPORT_USER_STATUS.NEW, matchingUser.status); assertFalse(initialIds.contains(matchingUser.id)); } @@ -152,7 +152,7 @@ public void testGetUsersStatusFilter() throws Exception { List users = generateBulkImportUser(10); BulkImport.addUsers(appIdentifierWithStorage, users); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.NEW, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BULK_IMPORT_USER_STATUS.NEW, null, null); assertEquals(10, addedUsers.size()); } @@ -165,12 +165,12 @@ public void testGetUsersStatusFilter() throws Exception { String[] userIds = users.stream().map(user -> user.id).toArray(String[]::new); storage.startTransaction(con -> { - storage.updateBulkImportUserStatus_Transaction(appIdentifierWithStorage, con, userIds, BulkImportUserStatus.PROCESSING); + storage.updateBulkImportUserStatus_Transaction(appIdentifierWithStorage, con, userIds, BULK_IMPORT_USER_STATUS.PROCESSING); storage.commitTransaction(con); return null; }); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.PROCESSING, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BULK_IMPORT_USER_STATUS.PROCESSING, null, null); assertEquals(10, addedUsers.size()); } @@ -183,12 +183,12 @@ public void testGetUsersStatusFilter() throws Exception { String[] userIds = users.stream().map(user -> user.id).toArray(String[]::new); storage.startTransaction(con -> { - storage.updateBulkImportUserStatus_Transaction(appIdentifierWithStorage, con, userIds, BulkImportUserStatus.FAILED); + storage.updateBulkImportUserStatus_Transaction(appIdentifierWithStorage, con, userIds, BULK_IMPORT_USER_STATUS.FAILED); storage.commitTransaction(con); return null; }); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BulkImportUserStatus.FAILED, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BULK_IMPORT_USER_STATUS.FAILED, null, null); assertEquals(10, addedUsers.size()); } @@ -248,7 +248,8 @@ public void randomPaginationTest() throws Exception { BulkImportUser expectedUser = sortedUsers.get(indexIntoUsers); assertEquals(expectedUser.id, actualUser.id); - assertEquals(expectedUser.toString(), actualUser.toString()); + assertEquals(expectedUser.status, actualUser.status); + assertEquals(expectedUser.toRawDataForDbStorage(), actualUser.toRawDataForDbStorage()); indexIntoUsers++; } diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java index 5efb99a58..c5c3aeb73 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java @@ -151,7 +151,7 @@ public void shouldReturn200Response() throws Exception { assertEquals(1, bulkImportUsers.size()); JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); bulkImportUserJson.get("status").getAsString().equals("NEW"); - BulkImportUser.fromJson(bulkImportUserJson).toRawData().equals(rawData); + BulkImportUser.fromJson(bulkImportUserJson).toRawDataForDbStorage().equals(rawData); process.kill(); Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); From 9967d6d923bebe34c8537def50750a9a1f1d723f Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 13:07:16 +0530 Subject: [PATCH 15/23] fix: PR changes --- .../test/bulkimport/apis/GetBulkImportUsersTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java index c5c3aeb73..8b40f96bf 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java @@ -151,7 +151,7 @@ public void shouldReturn200Response() throws Exception { assertEquals(1, bulkImportUsers.size()); JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); bulkImportUserJson.get("status").getAsString().equals("NEW"); - BulkImportUser.fromJson(bulkImportUserJson).toRawDataForDbStorage().equals(rawData); + BulkImportUser.fromTesting_fromJson(bulkImportUserJson).toRawDataForDbStorage().equals(rawData); process.kill(); Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); From 0574bfe55c39055b33158415c9af5f575e646125 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 14:53:51 +0530 Subject: [PATCH 16/23] fix: PR changes --- .../java/io/supertokens/bulkimport/BulkImportUserUtils.java | 6 +++--- .../test/bulkimport/apis/AddBulkImportUsersTest.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index 2d3f0003d..e94ce5883 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -70,7 +70,7 @@ public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifi } private static List getParsedUserRoles(JsonObject userData, String[] allUserRoles, List errors) { - JsonArray jsonUserRoles = parseAndValidateFieldType(userData, "roles", ValueType.ARRAY_OF_STRING, + JsonArray jsonUserRoles = parseAndValidateFieldType(userData, "userRoles", ValueType.ARRAY_OF_STRING, false, JsonArray.class, errors, "."); @@ -80,12 +80,12 @@ private static List getParsedUserRoles(JsonObject userData, String[] all // We already know that the jsonUserRoles is an array of non-empty strings, we will normalise each role now List userRoles = new ArrayList<>(); - jsonUserRoles.forEach(role -> validateAndNormaliseUserRole(role.getAsString(), allUserRoles, errors)); + jsonUserRoles.forEach(role -> userRoles.add(validateAndNormaliseUserRole(role.getAsString(), allUserRoles, errors))); return userRoles; } private static List getParsedTotpDevices(JsonObject userData, List errors) { - JsonArray jsonTotpDevices = parseAndValidateFieldType(userData, "totp", ValueType.ARRAY_OF_OBJECT, false, JsonArray.class, errors, "."); + JsonArray jsonTotpDevices = parseAndValidateFieldType(userData, "totpDevices", ValueType.ARRAY_OF_OBJECT, false, JsonArray.class, errors, "."); if (jsonTotpDevices == null) { return null; } diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index 94ce885b0..709455605 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -141,7 +141,7 @@ public void shouldThrow400Error() throws Exception { { try { JsonObject request = new JsonParser() - .parse("{\"users\":[{\"externalUserId\":[],\"userMetaData\":[],\"roles\":{},\"totp\":{}}]}") + .parse("{\"users\":[{\"externalUserId\":[],\"userMetaData\":[],\"userRoles\":{},\"totpDevices\":{}}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", @@ -150,12 +150,12 @@ public void shouldThrow400Error() throws Exception { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); assertEquals(responseString, - "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"externalUserId should be of type string.\",\"roles should be of type array of string.\",\"totp should be of type array of object.\",\"loginMethods is required.\"]}]}"); + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"externalUserId should be of type string.\",\"userRoles should be of type array of string.\",\"totpDevices should be of type array of object.\",\"loginMethods is required.\"]}]}"); } // Invalid role (does not exist) try { JsonObject request = new JsonParser() - .parse("{\"users\":[{\"roles\":[\"role5\"]}]}") + .parse("{\"users\":[{\"userRoles\":[\"role5\"]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", From 9b2a6593d07e48ef56aa05e30e8efde8a7141de4 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 14:59:13 +0530 Subject: [PATCH 17/23] fix: PR changes --- ...dBulkImportUsersTest.java => DeleteBulkImportUsersTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/io/supertokens/test/bulkimport/apis/{DeleteFailedBulkImportUsersTest.java => DeleteBulkImportUsersTest.java} (99%) diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java similarity index 99% rename from src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java rename to src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java index 4ac29e8ac..3ae498647 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteFailedBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java @@ -47,7 +47,7 @@ import static io.supertokens.test.bulkimport.BulkImportTestUtils.generateBulkImportUser; -public class DeleteFailedBulkImportUsersTest { +public class DeleteBulkImportUsersTest { @Rule public TestRule watchman = Utils.getOnFailure(); From ab7d2bf62b2bcb98fc2f551cdf5d8472143fc54b Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 15:14:40 +0530 Subject: [PATCH 18/23] fix: Make period and skew optional --- .../bulkimport/BulkImportUserUtils.java | 24 +++++++++++------ .../apis/AddBulkImportUsersTest.java | 26 +++++++++++++++++-- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index e94ce5883..c006c3640 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -95,8 +95,8 @@ private static List getParsedTotpDevices(JsonObject userData, List errors) { - // We don't perform any normalisation on the period in ImportTotpDeviceAPI.java other than checking if it is > 0 - if (period != null && period.intValue() < 1) { + // We default to 30 if period is null + if (period == null) { + return 30; + } + + if (period.intValue() < 1) { errors.add("period should be > 0 for a totp device."); return null; } - return period != null ? period.intValue() : null; + return period; } private static Integer validateAndNormaliseTotpSkew(Integer skew, List errors) { - // We don't perform any normalisation on the period in ImportTotpDeviceAPI.java other than checking if it is >= 0 - if (skew != null && skew.intValue() < 0) { + // We default to 1 if skew is null + if (skew == null) { + return 1; + } + + if (skew.intValue() < 0) { errors.add("skew should be >= 0 for a totp device."); return null; } - return skew != null ? skew.intValue() : null; + return skew; } private static String validateAndNormaliseTotpDeviceName(String deviceName, List errors) { diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index 709455605..b3e37cbf3 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -152,6 +152,20 @@ public void shouldThrow400Error() throws Exception { assertEquals(responseString, "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"externalUserId should be of type string.\",\"userRoles should be of type array of string.\",\"totpDevices should be of type array of object.\",\"loginMethods is required.\"]}]}"); } + // secretKey is required in totpDevices + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"totpDevices\":[{\"secret\": \"secret\"}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"secretKey is required for a totp device.\",\"loginMethods is required.\"]}]}"); + } // Invalid role (does not exist) try { JsonObject request = new JsonParser() @@ -444,8 +458,16 @@ public void shouldNormaliseFields() throws Exception { assertEquals(1, bulkImportUsers.size()); JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); - JsonArray loginMethods = bulkImportUserJson.getAsJsonArray("loginMethods"); + // Test if default values were set in totpDevices + JsonArray totpDevices = bulkImportUserJson.getAsJsonArray("totpDevices"); + for (int i = 0; i < totpDevices.size(); i++) { + JsonObject totpDevice = totpDevices.get(i).getAsJsonObject(); + assertEquals(30, totpDevice.get("period").getAsInt()); + assertEquals(1, totpDevice.get("skew").getAsInt()); + } + + JsonArray loginMethods = bulkImportUserJson.getAsJsonArray("loginMethods"); for (int i = 0; i < loginMethods.size(); i++) { JsonObject loginMethod = loginMethods.get(i).getAsJsonObject(); if (loginMethod.has("email")) { @@ -474,7 +496,7 @@ public static JsonObject generateUsersJson(int numberOfUsers) { user.addProperty("externalUserId", UUID.randomUUID().toString()); user.add("userMetadata", parser.parse("{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}")); user.add("userRoles", parser.parse("[\"role1\", \"role2\"]")); - user.add("totpDevices", parser.parse("[{\"secretKey\":\"secretKey\",\"period\": 30,\"skew\":1,\"deviceName\":\"deviceName\"}]")); + user.add("totpDevices", parser.parse("[{\"secretKey\":\"secretKey\",\"deviceName\":\"deviceName\"}]")); String email = " johndoe+" + i + "@gmail.com "; From 61c922502ffdac5cdfb1c3e2efd5127634ee3ac2 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 28 Feb 2024 15:43:54 +0530 Subject: [PATCH 19/23] fix: Rename deletedUserIds to deletedIds --- .../webserver/api/bulkimport/BulkImportAPI.java | 16 ++++++++-------- .../apis/DeleteBulkImportUsersTest.java | 5 ++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java index 648e1cf45..af2a29c9c 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java @@ -211,22 +211,22 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws } try { - List deletedUserIds = BulkImport.deleteUsers(appIdentifierWithStorage, userIds); + List deletedIds = BulkImport.deleteUsers(appIdentifierWithStorage, userIds); - JsonArray deletedUserIdsJson = new JsonArray(); - JsonArray invalidUserIds = new JsonArray(); + JsonArray deletedIdsJson = new JsonArray(); + JsonArray invalidIds = new JsonArray(); for (String userId : userIds) { - if (deletedUserIds.contains(userId)) { - deletedUserIdsJson.add(new JsonPrimitive(userId)); + if (deletedIds.contains(userId)) { + deletedIdsJson.add(new JsonPrimitive(userId)); } else { - invalidUserIds.add(new JsonPrimitive(userId)); + invalidIds.add(new JsonPrimitive(userId)); } } JsonObject result = new JsonObject(); - result.add("deletedUserIds", deletedUserIdsJson); - result.add("invalidUserIds", invalidUserIds); + result.add("deletedIds", deletedIdsJson); + result.add("invalidIds", invalidIds); super.sendJsonResponse(200, result, resp); diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java index 3ae498647..f4edc4ba3 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java @@ -156,16 +156,15 @@ public void shouldReturn200Response() throws Exception { request.add("ids", validIds); - JsonObject response = HttpRequestForTesting.sendJsonDELETERequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000000, 1000000, null, Utils.getCdiVersionStringLatestForTests(), null); - response.get("deletedUserIds").getAsJsonArray().forEach(id -> { + response.get("deletedIds").getAsJsonArray().forEach(id -> { assertTrue(validIds.contains(id)); }); - assertEquals(invalidId, response.get("invalidUserIds").getAsJsonArray().get(0).getAsString()); + assertEquals(invalidId, response.get("invalidIds").getAsJsonArray().get(0).getAsString()); process.kill(); Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); From 4a3a63d691675dfd3c80e9739b1102b4b09b206e Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 7 Mar 2024 17:50:30 +0530 Subject: [PATCH 20/23] merge latest (#947) * fix: adds test for user pagination from old version (#893) * adding dev-v7.0.15 tag to this commit to ensure building * fix: core config validation (#894) * fix: core config validation * fix: core config validation * fix: PR comments * fix: PR comments * fix: test * fix: startup test * fix: using ConfigMapper * fix: test * fix: config mapper * fix: core config * adding dev-v7.0.16 tag to this commit to ensure building * fix: null handling in config mapper (#897) * fix: core config validation * fix: core config validation * fix: PR comments * fix: PR comments * fix: test * fix: startup test * fix: using ConfigMapper * fix: test * fix: config mapper * fix: core config * fix: null handling * fix: test defaults * adding dev-v7.0.16 tag to this commit to ensure building * Add t4-app in release checklist (#899) * Update README.md * Add Dockerfile for ubuntu 22.04 (#904) * fix: error logs should be printed to StdErr (#918) * fix: Load only cud (#917) * fix: update config and validateAndNormalize * fix: impl * fix: PR comments * fix: cleanup * fix: cleanup * fix: pr comments * fix: pr comments * fix: tests * fix: changelog * fix: 400 error * fix: cuds from db * fix: connection pool issue (#919) * adding dev-v7.0.17 tag to this commit to ensure building * fix: Test fix (#921) * fix: test * fix: tests * adding dev-v7.0.17 tag to this commit to ensure building * fix: CICD tests (#925) * fix: tests * fix: adding retry * fix: kill * fix: typo * fix: cicd * fix: cicd * adding dev-v7.0.17 tag to this commit to ensure building * fix: Vulnerability fix (#928) * fix: updated dependencies * fix: updated dependencies * chore: version and changelog * fix: update impl deps * fix: telemetry data * fix: changelog * fix: cleanup * fix: active user storage * fix: active users storage test * fix: changelog * fix: versions * adding dev-v7.0.18 tag to this commit to ensure building * fix: Cicd tests fix (#932) * fix: CICD fix * fix: test fix * fix: test for mongo * adding dev-v7.0.18 tag to this commit to ensure building * fix: dependencies (#933) * fix: dependency fix * fix: dep fix * adding dev-v7.0.18 tag to this commit to ensure building * fix: dependencies (#934) * adding dev-v7.0.18 tag to this commit to ensure building * fix: 31 days of mau (#936) * fix: MAU computation (#937) * fix: mau * fix: typo * adding dev-v7.0.18 tag to this commit to ensure building * fix: mau related tests (#938) * adding dev-v7.0.18 tag to this commit to ensure building * fix: Tests (#939) * fix: mau related tests * fix: test * adding dev-v7.0.18 tag to this commit to ensure building * fix: fixes storage handling for non-auth recipes (#942) * fix: non auth recipe stuff * fix: user roles * fix: half done * fix: thirdparty changes * fix: passwordless changes * fix: active users * fix: session changes * fix: user metadata * fix: user roles * fix: totp * fix: email verification * fix: multitenancy and other minor fixes * fix: compile errors * fix: bugs and tests * fix: bugs and tests * fix: func rename * fix: PR comments * fix: pr comments * fix: pr comments * fix: pr comments * fix: user role multitenant tests * fix: email verification tests * fix: user role deletion * fix: user roles * fix: user roles * fix: get tenant identifier refactor * fix: pr comments * fix: query * fix: tests version and changelog * Update CHANGELOG.md Co-authored-by: Rishabh Poddar * fix: pr comments --------- Co-authored-by: Rishabh Poddar * adding dev-v8.0.0 tag to this commit to ensure building * fix: plugin interface version (#945) * adding dev-v8.0.0 tag to this commit to ensure building * fix: cicd tests (#946) * fix: cicd tests * fix: cicd tests * fix: cicd tests * fix: cicd tests * fix: cicd tests * adding dev-v8.0.0 tag to this commit to ensure building --------- Co-authored-by: rishabhpoddar Co-authored-by: Ankit Tiwari --- CHANGELOG.md | 28 +- cli/jar/cli.jar | Bin 47546 -> 47546 bytes downloader/jar/downloader.jar | Bin 15229 -> 15229 bytes ee/jar/ee.jar | Bin 14354 -> 14348 bytes .../java/io/supertokens/ee/EEFeatureFlag.java | 3 +- jar/core-7.0.18.jar | Bin 741367 -> 0 bytes jar/core-8.0.0.jar | Bin 0 -> 747495 bytes src/main/java/io/supertokens/ActiveUsers.java | 35 +- ...ping.java => StorageAndUserIdMapping.java} | 12 +- ...IdentifierWithStorageAndUserIdMapping.java | 41 -- .../io/supertokens/authRecipe/AuthRecipe.java | 332 ++++++++------- .../cronjobs/telemetry/Telemetry.java | 9 +- .../io/supertokens/dashboard/Dashboard.java | 142 +++---- .../emailpassword/EmailPassword.java | 186 +++++---- .../emailverification/EmailVerification.java | 70 ++-- .../java/io/supertokens/inmemorydb/Start.java | 19 +- .../inmemorydb/queries/UserRolesQueries.java | 17 +- .../multitenancy/Multitenancy.java | 30 +- .../passwordless/Passwordless.java | 219 +++++----- .../java/io/supertokens/session/Session.java | 302 +++++++------- .../storageLayer/StorageLayer.java | 168 ++++---- .../io/supertokens/thirdparty/ThirdParty.java | 92 ++--- src/main/java/io/supertokens/totp/Totp.java | 111 ++--- .../useridmapping/UserIdMapping.java | 195 +++++---- .../usermetadata/UserMetadata.java | 31 +- .../io/supertokens/userroles/UserRoles.java | 153 ++++--- .../io/supertokens/webserver/Webserver.java | 3 +- .../supertokens/webserver/WebserverAPI.java | 111 +++-- .../CanCreatePrimaryUserAPI.java | 35 +- .../accountlinking/CanLinkAccountsAPI.java | 49 ++- .../accountlinking/CreatePrimaryUserAPI.java | 32 +- .../api/accountlinking/LinkAccountsAPI.java | 55 +-- .../api/accountlinking/UnlinkAccountAPI.java | 27 +- .../api/core/ActiveUsersCountAPI.java | 4 +- .../webserver/api/core/ConfigAPI.java | 9 +- .../webserver/api/core/DeleteUserAPI.java | 15 +- .../webserver/api/core/EEFeatureFlagAPI.java | 8 +- .../webserver/api/core/GetUserByIdAPI.java | 21 +- .../webserver/api/core/HelloAPI.java | 14 +- .../webserver/api/core/JWKSPublicAPI.java | 2 +- .../webserver/api/core/LicenseKeyAPI.java | 14 +- .../api/core/ListUsersByAccountInfoAPI.java | 13 +- .../api/core/NotFoundOrHelloAPI.java | 20 +- .../webserver/api/core/RequestStatsAPI.java | 7 +- .../webserver/api/core/TelemetryAPI.java | 4 +- .../webserver/api/core/UsersAPI.java | 16 +- .../webserver/api/core/UsersCountAPI.java | 14 +- .../api/dashboard/DashboardSignInAPI.java | 3 +- .../api/dashboard/DashboardUserAPI.java | 25 +- .../GetDashboardSessionsForUserAPI.java | 3 +- .../api/dashboard/GetDashboardUsersAPI.java | 3 +- .../api/dashboard/RevokeSessionAPI.java | 3 +- .../VerifyDashboardUserSessionAPI.java | 10 +- .../ConsumeResetPasswordAPI.java | 15 +- .../GeneratePasswordResetTokenAPI.java | 19 +- .../ImportUserWithPasswordHashAPI.java | 10 +- .../api/emailpassword/ResetPasswordAPI.java | 15 +- .../api/emailpassword/SignInAPI.java | 18 +- .../api/emailpassword/SignUpAPI.java | 15 +- .../webserver/api/emailpassword/UserAPI.java | 50 ++- .../GenerateEmailVerificationTokenAPI.java | 13 +- .../RevokeAllTokensForUserAPI.java | 2 +- .../emailverification/UnverifyEmailAPI.java | 19 +- .../api/emailverification/VerifyEmailAPI.java | 32 +- .../webserver/api/jwt/JWKSAPI.java | 8 +- .../webserver/api/jwt/JWTSigningAPI.java | 7 +- .../AssociateUserToTenantAPI.java | 15 +- .../multitenancy/CreateOrUpdateAppAPI.java | 3 +- .../CreateOrUpdateConnectionUriDomainAPI.java | 2 +- .../CreateOrUpdateTenantOrGetTenantAPI.java | 6 +- .../DisassociateUserFromTenant.java | 18 +- .../api/multitenancy/ListAppsAPI.java | 14 +- .../ListConnectionUriDomainsAPI.java | 10 +- .../api/multitenancy/ListTenantsAPI.java | 15 +- .../api/multitenancy/RemoveAppAPI.java | 2 +- .../RemoveConnectionUriDomainAPI.java | 2 +- .../api/multitenancy/RemoveTenantAPI.java | 6 +- .../CreateOrUpdateThirdPartyConfigAPI.java | 2 +- .../thirdparty/RemoveThirdPartyConfigAPI.java | 5 +- .../api/passwordless/ConsumeCodeAPI.java | 16 +- .../api/passwordless/CreateCodeAPI.java | 7 +- .../api/passwordless/DeleteCodeAPI.java | 2 +- .../api/passwordless/DeleteCodesAPI.java | 6 +- .../api/passwordless/GetCodesAPI.java | 20 +- .../webserver/api/passwordless/UserAPI.java | 54 ++- .../webserver/api/session/HandshakeAPI.java | 10 +- .../webserver/api/session/JWTDataAPI.java | 32 +- .../api/session/RefreshSessionAPI.java | 33 +- .../webserver/api/session/SessionAPI.java | 37 +- .../webserver/api/session/SessionDataAPI.java | 32 +- .../api/session/SessionRegenerateAPI.java | 12 +- .../api/session/SessionRemoveAPI.java | 75 +++- .../webserver/api/session/SessionUserAPI.java | 34 +- .../api/session/VerifySessionAPI.java | 8 +- .../api/thirdparty/GetUsersByEmailAPI.java | 14 +- .../webserver/api/thirdparty/SignInUpAPI.java | 23 +- .../webserver/api/thirdparty/UserAPI.java | 29 +- .../api/totp/CreateOrUpdateTotpDeviceAPI.java | 51 +-- .../webserver/api/totp/GetTotpDevicesAPI.java | 27 +- .../api/totp/ImportTotpDeviceAPI.java | 38 +- .../api/totp/RemoveTotpDeviceAPI.java | 28 +- .../webserver/api/totp/VerifyTotpAPI.java | 26 +- .../api/totp/VerifyTotpDeviceAPI.java | 25 +- .../useridmapping/RemoveUserIdMappingAPI.java | 13 +- .../UpdateExternalUserIdInfoAPI.java | 13 +- .../api/useridmapping/UserIdMappingAPI.java | 29 +- .../usermetadata/RemoveUserMetadataAPI.java | 20 +- .../api/usermetadata/UserMetadataAPI.java | 35 +- .../api/userroles/AddUserRoleAPI.java | 7 +- .../api/userroles/CreateRoleAPI.java | 10 +- .../userroles/GetPermissionsForRoleAPI.java | 10 +- .../webserver/api/userroles/GetRolesAPI.java | 9 +- .../userroles/GetRolesForPermissionAPI.java | 9 +- .../api/userroles/GetRolesForUserAPI.java | 8 +- .../api/userroles/GetUsersForRoleAPI.java | 6 +- .../RemovePermissionsForRoleAPI.java | 11 +- .../api/userroles/RemoveRoleAPI.java | 9 +- .../api/userroles/RemoveUserRoleAPI.java | 8 +- .../io/supertokens/test/FeatureFlagTest.java | 34 +- .../io/supertokens/test/PathRouterTest.java | 262 ++++++++---- .../accountlinking/CreatePrimaryUserTest.java | 19 +- .../GetUserByAccountInfoTest.java | 148 ++++--- .../test/accountlinking/LinkAccountsTest.java | 17 +- .../test/accountlinking/MultitenantTest.java | 190 +++++---- .../test/accountlinking/SessionTests.java | 111 ++--- .../test/accountlinking/TimeJoinedTest.java | 36 +- .../api/CreatePrimaryUserAPITest.java | 5 +- .../api/GetUserByAccountInfoTest.java | 16 - .../test/accountlinking/api/SessionTests.java | 17 - .../test/authRecipe/MultitenantAPITest.java | 42 +- .../test/authRecipe/UserPaginationTest.java | 16 +- .../dashboard/apis/MultitenantAPITest.java | 6 +- .../test/emailpassword/EmailPasswordTest.java | 23 +- .../MultitenantEmailPasswordTest.java | 159 ++++---- .../emailpassword/api/MultitenantAPITest.java | 64 ++- ...mailVerificationWithUserIdMappingTest.java | 32 ++ .../api/MultitenantAPITest.java | 5 +- .../mfa/api/CreatePrimaryUserAPITest.java | 4 +- .../test/multitenant/AppTenantUserTest.java | 37 +- .../RequestConnectionUriDomainTest.java | 26 +- .../test/multitenant/TestAppData.java | 55 +-- .../test/multitenant/api/TestApp.java | 106 ++++- .../api/TestConnectionUriDomain.java | 3 +- .../multitenant/api/TestLicenseBehaviour.java | 2 +- .../api/TestMultitenancyAPIHelper.java | 121 ++++++ .../multitenant/api/TestPermissionChecks.java | 18 +- .../test/multitenant/api/TestTenant.java | 3 +- .../TestTenantIdIsNotPresentForOlderCDI.java | 17 +- .../api/TestTenantUserAssociation.java | 97 +++-- .../api/TestWithNonAuthRecipes.java | 378 ++++++++++++++++++ .../passwordless/api/MultitenantAPITest.java | 42 +- .../thirdparty/api/MultitenantAPITest.java | 2 +- .../supertokens/test/totp/TOTPRecipeTest.java | 3 + .../test/totp/api/MultitenantAPITest.java | 45 ++- .../test/totp/api/TotpUserIdMappingTest.java | 4 +- .../userIdMapping/api/MultitenantAPITest.java | 91 ++--- .../test/userRoles/UserRolesStorageTest.java | 16 +- 157 files changed, 3674 insertions(+), 2472 deletions(-) delete mode 100644 jar/core-7.0.18.jar create mode 100644 jar/core-8.0.0.jar rename src/main/java/io/supertokens/{AppIdentifierWithStorageAndUserIdMapping.java => StorageAndUserIdMapping.java} (69%) delete mode 100644 src/main/java/io/supertokens/TenantIdentifierWithStorageAndUserIdMapping.java create mode 100644 src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b6d5c90..aa7835dc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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). -## [8.0.0] - 2023-11-29 +## [9.0.0] - 2024-03-04 ### Added @@ -35,6 +35,32 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - TODO - copy once postgres / mysql changelog is done +## [8.0.0] - 2024-03-04 + +### Breaking changes + +- The following app specific APIs return a 403 when they are called with a tenant ID other than the `public` one. For example, if the path is `/users/count/active`, and you call it with `/tenant1/users/count/active`, it will return a 403. But if you call it with `/public/users/count/active`, or just `/users/count/active`, it will work. + - GET `/recipe/accountlinking/user/primary/check` + - GET `/recipe/accountlinking/user/link/check` + - POST `/recipe/accountlinking/user/primary` + - POST `/recipe/accountlinking/user/link` + - POST `/recipe/accountlinking/user/unlink` + - GET `/users/count/active` + - POST `/user/remove` + - GET `/ee/featureflag` + - GET `/user/id` + - PUT `/ee/license` + - DELETE `/ee/license` + - GET `/ee/license` + - GET `/requests/stats` + - GET `/recipe/user` when querying by `userId` + - GET `/recipe/jwt/jwks` + - POST `/recipe/jwt` + +### Fixes + +- Fixes issue with non-auth recipe related storage handling + ## [7.0.18] - 2024-02-19 - Fixes vulnerabilities in dependencies diff --git a/cli/jar/cli.jar b/cli/jar/cli.jar index 679236a42d96bb6b860fc324648613cc7ff56934..fdb82869bf8b5995d57a646abaf8de863dc88f8b 100644 GIT binary patch delta 835 zcmdn>nQ7N&Cf)#VW)?061`ZAe-I~;iylTuK%FIhmrzSOm0SK5t1Q0L-ab~_gGXp{c zP|3t)I$#Y~4M5an4n})spy*~V#wn~I#^i@=?qC6J_B1iDiYc6X0j7l$;ZjhAu_k8v2$T_1Ls0;0B%-V_ygh_*~J!Moe_&UAoeG(_y7`^9JcZ= zgkEI=rh8X`BW3dWReGTCn6%moENr{l4ougr_60}Pxz&ka+GtHEnC@DW36}r1CKycn ztWAZ`o1`b7UuzC#bFNDU)A{S-!1RT6Gr@Gu`f4!!YJE7EcG-{%rnhVe1=BnmA!b)> zOat5XaAO8o+-Fk|m|nOEVy+L+E~U*7yJ9v ut0IT8Mk_M*Bwm|H-u*D0^m)#l- zrmMF?-1~Sd#Qm1r!ocE_wn6mY-3Cr$lMS{*%$d0zoU$fA-42mA*pUQQKXr#2n7+3I OVvfm9NVt{mbOiu8^dPVR delta 835 zcmdn>nQ7N&Cf)#VW)?061`ZAe&G6`nylTuK%FIg*NJTIJ0TYM-0%jo2%-3gTK-I8J z2dv?$0f?H+!DtVnH+wNoVFe33WOD}#XtSq@fd!_>x`GAn$TsqV1wO_bPoAtT2o_wf zy_*Xxm}(XP7T967pApQcvUnf@mWlQC1PiS6T?Vtkc=GvRj>)~jJYb1A!8L4PhIEu9 z3z%UZw;gQANMr-+U>@ z2V%Tp;aZ4k7t3tG#tW3kfPJw!zpZ~$&rTKEIv8`;GcV4V?*IUx2YulN8Gm>jn9FN9uY0;YRcfg@$|`Bi!# z{-o7bU}4+Uc3`@0wJ(@Hw>l9_8?6Zi(_L#a!SuH^!C=~FZ7PJ`Bt7~3T5~X)b6ql+ z&R-V?rZ23U38r(_SA*$S>%+mc%Z6Mqy=4Q$Y@Uq}vnw{HfyEzg%mC9qn}Wdf!c7o! zeSmf;ZHCwtvl$|GXtM`c4eu6+-m)zaJ1%VT0*lLTjRw=zTOsa!ycObp%WYv`@k!et w`tNQ7r?JTf+ac!6+zw7zlb>#f$Q$fP0;`|8!wpQ|+W|4hWG5utN_V;f0ONHJGynhq diff --git a/downloader/jar/downloader.jar b/downloader/jar/downloader.jar index 4c8bac4ca6fb7956cbb6fb5c2bafc21a2298229f..b39a17b48c74849fcfa52cbcb83ec409533250d4 100644 GIT binary patch delta 375 zcmexc_P2~Tz?+$ci-CcIgF(M0bt11CGl(+tQUg*E3_!pHB7lGyh%@u`nHf+uEYksN zxM~ifHuE#CWCJlK?`C%f3ovp7vqA;5C;OW7Ozz|50ZYu~y~zb;R7&(Qff-!VRba;C zc3CH|z!}-;5ScWE;}C{{avL{DYVuA!N3fE=dTbCqY=-t=0T07!X0VE5CMIC|uL)RU zvc0Jum|tLO1*X@Ux`OGirmoM1kp<76D-T ijYSrij delta 375 zcmexc_P2~Tz?+$ci-CcIgFz=edLpkHGl(+tQUg*E3_!pHB7lGyh%@u`nHf+uEYksN zxM~ifHuE#CWCJlK?`C%f3ovp7vqA;5C;OW7Ozz|50ZYu~y~zb;R7&(Qff-!VRba;C zc3CH|z!}-;5ScWE;}C{{avL{DYVuA!N3fE=dTbCqY=-t=0T07!X0VE5CMIC|uL)RU zvc0Jum|tLO1*X@Ux`OGirmoM1kp<76D-T ijYSrijWNf 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 delta 11398 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

hyS!JGOk@oa%&+fJ`@aDFi;tmem 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

55s%VG^y; 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

;#USQPO0reOuAV{pcmZl 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'_LVJuTjDua;5gA*Yein_!W`!%=GwRT64M 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%0AI-mn|8*TNy_shZb{g3vXv)i-G)Sht`teqx4 z+QHk(ljM>85=Lrij~&5ZqKhhynd$$Y;O*aXgD`S;cU(r;S+zQJTp{yO5I-r+MzlbqfB(wH{^gj>+ZoARXZKudQ)4o>iMQhq-u+R|VE zwnraqNmdDuIlEsHWFe%#A+B8*d$xik&7>nGN3F!#eScCY8FF)Oxu)Kc)Ndrpg*YFDIwyd78gZI9NFHTly+UbgL;1_ z=F)jc$wk&3>FG+Y-;u<1HEx>Y^Fr4_-()9%^ZRtcP1%|}%zd8K?4!%+o$a?#>`tO4 z`w07*?D6x9886|8`Ua}=`{85aD<* z-^BnAy&-PH6Ig#}W%rHFFXrnfy(!oGh$WUwF|yx9KF4POH@^(Ce}eN1g&1BC=+{r% z-Li66EjLtzbF;-y?%hKMoF6U$P|-Su3TjM4-hfpl+yt#-s;W2Z zhz9npo!%zv&CJX+{6>J3s_mmtsP#=ab>9_3Ja&G^TX8B^EYo#}SO49}aVdKCtB&ID zBSE_k#2&6~jhZ*d&=eDoA(MFG{{75Gl189#c_`s3DCb&GH>+Nsb^h_v-G9bf^j`Gi zEVI}fJyDzrnO4K7@Rtht&Yt_8FrBVTy3{fr89l@2bdb-=9B#;}^-1nE?x)U}NyGA% z+_*su?*2ANKIvnjn*W~`j9H#cvnF^vD;Qd^P&)AqD?U)e>x{+81K?xi@)Yvu;fhG^6ytsKViX|fCtQa2~ zIC~M=a4qP&Mz`*C{=ujcvYSw8nj%THRM(muQ`tgj6!3MihOlL-_HfnC(U%qqxzkEA zR%sIz5^16dBTXq&dG)om1y!a;$I-;eToFkzK{-Vwm7x#}$}s-&!?Dilc;iTIGp8CJ zwcBtRIj!Ld6^eb_x=}BYSEEuUKIR1_R9fnwOfz!@Ftwkq1_!HAUy&5Qyj1~0hA}yU}}SZ{ZUiZ$B7%{)|D0M{vXQTDk{=7 z$pS6h-QC@#@PZ<7cXvoA+}+(B3U_xa+}&LYcXxLRE~jtLUEOEqu9<%LbFF;H{~_ZG z`6Bj?Jxi}1kRQ=?L_VWEGvE=}$bj~uLBp;zxQTXb`C6}0q<%+XY0d-)^ZFjr;fQ+< zB}G&SG9x5z`_A#lNAC%;gE0z`w$PDW^oXHR(}5ol2d%7wk-Ld= zSDd;qTT}FzR>_pRHni?CLy0`j3XmW2Rm!v~ve#WA4K#XeG7Z8F!odIl)8OY`xp>+Y zTC-OBE^f?}|A3_I)~AW@47jFda{jvA@ND;K?*uv8=@tYDhL{_G42N%k@Y&NHURY~O zW9+9lpw5z_NPvUPUwE)e)Yr1rPTXeA7#*HFR_vE>WJ-KU(mqu|hsvE^Y10K0^P=h9 za>lt5W=>30f98`Osf_VPr;?ywXdjz4{S)2j1^EjLLRpDGf@1KvPr0Ad6Ze3ACNhLS z#yGeuz{H;FYv1ybfQ%y6B)Q~n>d{!FrNy6JiQfcKESNkv!aTJ#W799?@BzHalKmaA z{{H%IGFTz?+o0>g|yBB}1brKmgJMM&}wJ5`>aj3PLBVqe)M7{^-P5<_ujuM(8VpTp5aPF;G&Ft55qi3!uc2maD zH^fnc*KV*ypz}d+88r+xByXY1k{Y>M$~s7o9!F--$5=#ItFcA1fKS)m65fr2-rdT) zBKy&we6$8LJpo*tWajt5@jt5XcgFB$P*#i84Q z)9cqx=tnobld|spx*;LCX9;FBB6pNN)k7a#rLsRUwZ#7Og6Rf!gGtI#EfTNra1W6f zGi7Z(T#f_L#XMy?1&z71W>{^VOubeT)D)KFF5h2u3IiZa|9Kt_2~OmPbRP(D|8= z)(Bw-u{L~nfcz21B}JX%89m3h%kH!qeoJ^kl@a~mXSoY!o;`VF&%;%1RslntD`6ni zg-B>B`9jD(Dun+2H=w7H>yh-yX9NiNJYs_XOZ51+2=EDe{&apJ8{l84!+*i#R4T4o z&wWS!mhO}xwXP*8)mJIM5})$9YPDO6JW*Leus*On7T;sR`gA33k*2Gk4ha(#$@2?Q zP>P14LMfB@Ro&tE@^Wf^>YwY|RhKV1Pfm%%bhdnn+pzQmP(K8z5aatMZ?9`%9Y7vy zWgXv@zc;Sro&KMW@OtClyoCvSdo=j~BB?7#d3-4g~3!Czledb+9Wk z{Y)(gQvlU1m~b{?pTW@L?TT}`o^wamW8E%9Uh{B zc~~rJ8~|jUXu;dmzOS*KGs?`!h*;#)9zyVI(atX0ydVXsrMs$iS|0vTbY3L`3Hb(1 zkLZ;5P*iO6lK^~I{Th6hIaExrh&wAx*1I68J7HU3yp>LVQ?6K}B+IO-#gpX&rLExj zj3(hxDM8IvF6X{!-83qITtS10QbA#RRGWeUVNPrOv)!&lsy`kS3by7+5u`u5@4HGgb9LZ>dk!3^N=LXL_SW$ zROV~liZiu4&h0Sd@P7D+Lm$y9^I&op_|GuLo!9s_D-8%;bm~3V`alh&2K*%C3T1cDa8kFFv2SU9 z@5VH&OHwo zBgKcVtZNoeN+2_aUj4HQpHzw{Kot*@(MR}^<|*Cl`6^IO?D}d~CU#RenwV^3=#}Mk zlddNgH}64`&kE9{{*CZflz<=8TfR~00snzc9q5sr&o9}yarOr8d9ryp9bkkZ#O^cQ4@*L?T&OEiW7FP3U|;w_`UEJ!9lO;sElK|hTR*H+7!P+ zqb(ZY-Ceu<*BRP8@#Z{_fJ;WWM zx*I04g7xYPVPChAI-*=9D=X`9>cz=%+R0+fA=Vce%cNMr#!zh>H+1NGi*IwZ^D{qr z+TmPJqN1RKlENYdh=F)*0i;RL1V%rlits%=`r?$7&etlV#7KCHpAKhB5vQcPJ7(gX zchPn!SD<$FdQ^sLXSRKfsNV7xNv2s08W@^%#k_O+0&kOT9+56@2Qy+VR#DJsz0j`B zSaIwty77?98Jbr_p)W8bQ0ciz201M$^t+#LxQ6-nO5{%my@LCv z5!61t`+p#r{ac~y6eMK9Sdi&|P>=wHEG1&U z@5JtP^ASVC)P@S8V2P7QPxkv7a72AW?(D&G1%V3e_k+5EN8|&WA;h%X?Jd+wws+sdh9COWZJ>;_Aw*lH<^Lv$_wjtN7;8HQ3C)N z9murg@|V_iq>x5p&VXl3Dsn_VUojF6KS_LUeJP_UQxmI+p~F>PUz!J{yJhoi_o?4MOzWg;C~QkJ;blZRosob670S>Shj2n9;w$6< z?Tnd)BDc+?0(5S)4BB4Sky=XE7T)X*5HOLf9W0w=KMnSY9a>nW_MOIj`b!v{kef%} zuaT7e?gqc|7q?h%ro2%&&L#y3k*^uFf!kQe*`=qRaWQjw2f1!bA<-DEg&CbOBX%Kp zUN~QOOn!a&TY{8@z?pXa>#zNEq5mM3iMZN+N>~E_;`sjmI#8UHET|xI@JFy9xPtC= z80879Iu}%T3U(|I-Y!43*)n(Gx0`XR*!4e%X2g_?e7-}r+bod_yJS_`Eu~dur7!Qt z?|OfEP<>6D80;~91t@EZL5c1)o^{(=R}Ipj&_6tgt!*~hJ%&Wb2HrkJgfYSs#DSXG z5vX7Omj_h^RiAx&5S}pErw2vY1e0o0ld+u8p>TU+pE@(6c9BaSYPT8!TStBjbmNnK z3ewCRRk(W6cPzc8E-h-h=Hk$!DA~IZlZsSy&gHHP1wV5fn_rxQxERH8`*gJVq|Z~q zr?&30L&7H+u%LU)Vu{tdQ0Ad|A1D^j!Ugy53KzbVn;lC@7_M*lXg3eU3Lx&7Qn$`_ zH+^JmuvF&E~1XU{Ec-`@zQ;+^l4wr zPx}i0-`xlBDNf44F7g>DfuCHDzlFv^g?~_XzJWm$_>+e6?F5iaB9^W$^2`pRPD%c;hbFYfQQ|4^< z&o@Zt?%2zClWrA#2Tt_Tgyi(qMGd1tdFwY-vlV#JMZAQeo$s`E*t(H{KK)|WXD8eR zQzJoFhlMB^k`py-PkIi@UEGmvr(@#I_edL}%B;UlYhYF+L7nz$zm#!^LePuk7iG8? zf5o6lg1$dy*h#O1s`>r$jn#$4Cv^uj9-WR^UIg=NB21-c&2hySJIqqiw1p?VbAo@* zYh5VS>$smrtNMHd|L;coyz)4H@)ATpIn#f)*nia(kLU`>?9=1$2y0-zl+-GkiGD;k z5TZI>orTViVY+>EwxDF?@Sl#ze1)CVr&JC@gsa ztV{3pqV|ZX6|(;h5_*0Ut~S3!#1Y*6TYzyI;6M~a^} zr7vHmKA-=fp!h2cs+l>O+Wb{XSUVaRIZ9ZY*!;}~m9e~^1qF8C;gkglIBMPWG>HN@ ztfwz=+IQjUa7a}1AG^qXhGG^>F&4H#8NRS#{$RH7u+e@`zkG$ogxDS;`C}^F{{=ISpVDLllp2YJ%g59!P%Yof7yhtMv7L>y9RC)-y5ti=Autndi~eWIWi{}m zbYfQB>Av0j6v^AAlad4FYWRx*SVYN9OMjIw?xra}?Em~|Lzuf1GN<2?gu$jY zzdOHFXv&?(>QCNIlo4lEkrqut=6711pigu*;~nq`3ImN3?t0CSJaje|T4&!Dy8L#S z)pW@Bxc2_^e2?eLC&>!Hjzv~w4uuvnRP(7WFS7nRKXJlX?FJu*(Z|{j+V`DOD~u#O z1ze*C89kVyPjy{2fj6sTh(XQjr0TWD`GnF%=l+~qgsq|@Ugywwy&y4QA%@Te2f!lxy zQ$4H}ACr7d)fOtE1N*c9XS=iBxQPz-AT+5fjyX_@|Ce3w)Moud`P%YqIb|mK*S4Z` zjCGbkLj2$k7f6}jI_EfvJafrc$|?Dd6Ol;~hma?bzV?R?DJ-t>O-jDyh}7-6;>8Eu z(}^6HmIPny(0UjXabx#c+T`;jrQOru(2U~{cLib8G=#Ao*%5N4>&b%2EvcR$!z4;R z;jS;Dm(f|Q5@6(W-E(E}jk2l+$qqyMEkR7N{BfAQI+|j`$@I`lz(~W(IF5~nK`*{9 zo)SP~pkA*~U06mNI!SwxEXq26kL?9?a14B+~cm&RApG`tQ;<02#x06o! zhAO$rU`iF}-^D)!w~|0!2)#u6H95Z{bm?zRbABnbOz+Ro!}Zzr`X7jX|8}4Gx3<^+ zd$s&41pRfB`LAV#iX)O>g2-N(sTI*0YcA+~WFIRls`rDhgIDm#@piqnQBl!L z-KG(+)-g(wH)TG8WdWM^?%!{CCA=o9&XJgu9V{o!)p+LK*eD|L_`5Uxxgl_(op(Oz zJ$>d7K#^qTd~+46gcQn~G`2k%A&xc;wBNKAW4!DID7#g{;-5l4{!p@s^YS5mp7 zIN&wVumjN1R4{|i^VLMDtgMu>I#sOHve40dgqZM|^s+J8AWs0se~Lw|xbCvK ze5rBKVdMG`BIXS8y~VzO0Gyguon0K14)8)#^|cDMdTddi)n%XNq7~WSDR1ZyMn+Wr z1`e|{HZu0|IvG4Yea$cSL%E4beRdBPa`6T1Ue|?Lops7F0p1z>^@znN2A8wbv3F)@ zNie{hw!l0V@|b1V8~_qi?HB8|-Wfb?9#%1nm^!T&M2~7bpU;rcFgBk&Y zeOv|09twH|H@Ru~n+pa&)>}kSC4=QttQ9}FJhr$8nkz3~+MmRydts}}zS03IwCxB1 zrl5y_)g8HLPp!e@*UJ9&PptY&s~ZkZSI_<&-kMRBL&N}jH4>ujW@|`J4iBNM*ww?E zg-SY&?nQayj#aFiikoJzh;O+IknFsXfq=$91?Z}`vWF8Ixe_YDUO3EU!5~9~=s?tX z37zh9*cQ6Cd-4Pq-r<5Kc&8ju*rw!2r=#PNf=Nn%u1H|V^M!uGCKhlaO0eV@CjVmY zbWMf?sl)1ukCVBf)GS(neQxutJR8NktXBaVZ51RMsRWo-?w!RueFOt}JPB^V*E>z! z^;5UHt!KXy1_dcj&>0&FZAm@)K*KP8(tiQuLvvBGx@wn|SeDA18HUm&gMvj_CEXhN zWrm)G{ZwmTteNj!_&A}^QD9=HU3dGj@`io-jR;|g^;HR_=wU)%LYAY%_n3BkTAva> z#MaSf1Tf5wXV84RL^#7)4HjsRr`&6a5xtlLTZTgUh=9+n?kR>Z>~B7z*WGFZWSV*4oxX3)p;+JV8v)Ml(UMw5B#F zm01m_tmt$*;b!Su!Qg)3l6o5RH0_*6|E*++Qm!r-r+hvBA|5e%?s_NB@$;`6IG&p^ z+1d+M?VCAm>Df2_kO^^nHawXxx<;N2;x^e`9@T*hwYPJnDmYLDa z#WAt_Ko+Puwinq-YnY$+Eb}V{-!ck&mvJ+|1O%}&OQid{O<@V4pY5uFbrz; zr59UpGobK>WfC5Nc{HR0cFF60JDf0U5N9NeXMPUMT~kW;KXA3-lZlr?asS}-sz7;cKrsT;0f0DVAvMIs@0}Mi<(hA*mt zAmLH-aMapQYgIia%>m~6b!HvGnR9Ey3lzNy!IC?wt^1iO`AywAxN8Vn z)M>40X>0s9NCPJ8C;OgmLr>=Nl!9ydJstQ%2}0g~ND|z;AiDzf6#fvTdT*y-W;DwF zz~6oWQ~z~8u(X1rJ_&ERuq9|h%*zjE8H;35gWz3RF2tYGM$6|{#dnouqOp0V8gynD;H~?u-*#gqZqJR^IC*#<&4y3Pz@>9#}7WBqPJP)4Kp=eX!&c( z{7TDplJD4`GXE=Z&efJmCsFV~hUa3_;)>70+*d>CR`u1(+Fp-txvTU_3_(|a)?qh( z!}>}Jw;kRGAYPhZvb1Je@ulN~Oanx7C4m{+m^mJ9b$jXx3*)`4r~C@{bB^470&LJm zWlXc0BlE>$_k-zq{4`wH7QA%8Ue88|Psx?sWQM?z-~m1(?n@eN$aW$d z36Xu-o5B<-(BU5Noqrqrd9$M=s}JU;7}parJAs!aZ?WF#00mkNA23pfo`KLASgrh| z8DO)u8XfWT8>r|lpt|YQElOZr*y5)iW!vkv2Zr}_P80&i*9Vr4v%)b_-HRr&Jb8tg z&jId48``Nq&<#DC8VN2|88h}+)uUZkE*8fvUMd6atOhkFrM zhUi&b(Pln)(M8!DfQ%HnPi2G)Dl0h%4_*@x{O9X90LuT{mi0~iWRZIZ9~CMc#f~yH zF5)zE2wCBRm*%fAJY_rL-aGJeItNAFa~O_RfKyQ~US zwdhnOuJyq~`WlZcMN<0h+LKzS3m4PoK3=hWT5_S~;hmDoGW`;?vcGL%2_ruHn(`&z zk%nb;A?VMdST;#`)7F3&^^eKzLP(gqw3<*L&Zl=}D* zud+38Rd+1Ib4i3U$y3>tmgRR3zHp$HvsWA#2PGb5>3YFV4!F@~`$dcV%&RJ%+hAcXwD0X zWy>KGzRZ~lA@!eK^)Y2~U=M1U%nJ`z} z(6=wrm5BiwsbA%DLxSL6+3(tnKiFK=JvJ28cXGU`B`eB97$Awgkb;^p~;9YTQ(^OqIW`P z)#YhjH+YPH5s{WZZ(G(=87rYmRw+=h`M9eMY|ZG##jH7fL@p|p35;F9UAvF!N&+@& zLgqe>^zu0100MQBE$!IO;}lCW&;1$mnY*2^%OB$dU0o~oC|~;22r0x)f{PM8uJ`-+8sa&aKQ>4bE1XZ& zQ?L)~ngU#En4ka^Xt`$mMD43G!H?4_c>u9?&$>(T_(au~U=B^c?$_IuM~Az8tGa&iE_H#N~eEc(02Fdnl0sABs-ok%OMw z{wccHPGn;gJ{6gR8xU$*@3lYA_z4i{b?8c5;d^wc?28{0%ga9U@Uot-1*c}N`lCNP zE9J-;{j(_IGI<-oGL{L6iQIB#)iDH4O@}jIcyYvNRnhJ*xZkIht3PWDD;~jcJ!1Uq ztpa9|d0CK^Yq~eh0UP%59-BV5kPGO&3Q8&-*2%jVm)XFCbd7e@*N$KTeLNXsMXzoV zL$k|Z+Nk#%bU#1|U?xz6e4sgZbh!M)T+KH6^)7a8LvPfkBuyNX<7S+HnZRRmm6!@b z0F@?fT38@(JJXWAB6OM5y;H6fxKm^ZkgOU#OsWj1G465O_Huqp+yxRsmhxexrX4ZW zAw!BNC2;vS?;@iKw+F!xprKGR9H!TwIV-d(TS;EQ6zyr0YkL;Z`-;xLfpn|U9 zXZj5yf2b>)XSVm|JA7q{6eF66E@RZisS4v`@s|T1KHtGMmcASrH1-TTnZ+G(+$Ym^ zh?N_87@Vc>3#yp(w8k*YQ^zmO{ywvhWs$=~=udy=1QGX4SQ*A*NZ-5la42fD?6Zvw z?24h{r18MmWoOBnbKTYx5tNXg?w`p>D}r1r{^|jW-sb6$*0CWij%l2j8gV0xDb&Wz zv|{zbNNHwRmjcKg-`7%`fD#x+a7@l;GS-n@=x5Ke<~HG+_TG_O?1v{ztVA-*Gvq^# zL?1FUOezr;hkG`{XXq`s!1~d8HAmxG#-)pn{PS1nKdy5O!hkpx0%NO}uwU$`EhtD>S8Azmc41wU`#O8Du1_0B z^?|A${@0Tfj)R6Rd@Cn#KVkyfUfu+&5(vi0I=o&>((O0(UGINs(ZQ<;0C(v*_=5&R zf_Ci0E-LtM*44U9Zf6Kut8-7D=grj;lznMsBc3(D<^;(-3J8=T3b)1jVcny9A{qDRs}5BsX!B7nOY4Ju=Ofjt%0c<;-!}Agl>=PJ{$!Q-??+I@YYMf{MAt$<3sh6!`voi$~;! zF??eV($!r$0QLF&u#BVR2=J#TUC`)T98^ImD|-n4i`jVH*W)ytu_ zv7h2>xszC)C_GuRXXh&{)g^dTQ)BKQP69Y}sfEy)4oFwMA+#K*Wn65!7|Hhr5UM5+ z28jm=q1#MjScC4!ck-xM+7b%6b5niDC#X*CUQgN){^&lll5B30M7K&e{jK+?6{TM9 zJ0TUcTZkODLnnwzj5yC%A&|hK!CohsZC6)7I~L`Gu4uxDc~x<&#;(~_3jiG#<>J}K z8+x8;yxAY1;E6q|0K@hH%%ql)I~IV&%+=>Bin-(uDC9^lPQp_ zMm0u@=27R#OV#@8kFxbO)84pfO7gVZTE7xVmMyB?ck+TB(OQx=>Q3ldAb-r~{ zU~!7yM<{EmnjP)__(E>ut zp!i2P;Y$AsVx_uYOS|{74stHlv&DMRVa?7lFCIEkbf-FQGdjWTFw{36ACos9Uc6&o zo3qEw+Y-T>miYc9c3)egZhK`Pj}vQ93+<~Th$ii-zh+T{M3@GG9HOI5uUvA8PCTIZaZe(S(I`6=ajT|+OCiJ zgQBWMXG7h+g|&@u^9s&?tObXPG0qptgh+hBPE+-+K_tOy~MTcJdt| zP$qeiLtjio%v6JXNaSiwcfhWhT7TVSmW4eI2pmlgx;V?UGi>DYMqR#`?Og6Kv`X!^ zIm)!tY)FQ9&Cg`>*0Eo=x{~RPh*F;>b;+`c`zQ5HXQa6bwx=W&z}NL9a}n#{#1CHz zxHyyr-6&PY3uBjD&9ZIO4M%bsD6W1e+G@59n2(O{R4|uG681)d*|VyuM29*^^P$MI zudjmThgs0pDi#YOK zR`q{JP1{gKR!(I6A~-l_wDe13T`+UQ4bPCMtZAlSF2#&@jBn}*W7yiU=tJaMfL-IU zvPiX(TGi8V{4we`xrg~=2A9zV#;BMxeo+4nfri(>=-j#4vWacP%xOo&v4z3V|l?N|)c2hHcQ*kTX#Hl+h7LH1gq*=V0&VX+otw4nt4Obsg$D&&(UoJP!8`lZ9 znMB-$#5C_zv$u}$cQ<6%y>v6eBcJ)MB25WiU{ZNZz4c#{Q{SfQ)n87FdWI;WpfHEca3 zRmit6a;$qeS>yze5p-sN+(cd6P$FWb{SWIN8wvrW-s)ij7Qdmnh>E28UIlihJD3^R zN0C1&Wd$FUxQ=Yc@04MjT0RtchwP;iI@%*S;tG{U>^tUQ6NWp1+gwZ8DtH$aq3t*Z zUc}+!j373YO3h@w#2x5%;)nz&-{g{`r5wLMj|IwaEE4W}f#1A8Q9pC?@#fTjF+|Ic zfWA4LA7ZTX%~W(!*rq;KJwO*gkZ4xf;1|{%tV7c@AMvsY`TbzNAzIxO+pHx~$SW?z zJ3=EsDS=xI79H9~3{cFQGUbML68tM6-YEnw`MCgPL&TqDJgi{gH>%ymC41?SN0~}KySyR0u zNrf)nlyLW-R~@p3fAGRCWfc3g3KE+n6l~)2HctKWFyE6g?g4=C7a1Um)z-vAVTjMExs!GL=n&7Rj*A4WC{js{$!9#k))V zVNMQ}7HIKHP*>oaM-l@iyF@&DY>^;eJoUbQ-Ig4W#zT-JXY9@pn|oaa&q4}NGEP=D3DL8|c?l zPo!NmHK(g~53#~ujm&+dZF2@$1twumT(lxL%D{!wGwOzVI@ubrUw{ z(f_Pri?(v@-&JphVP9Sg2x^P!%rn)Cn}Nb3^Qwt^aW>MxWffVy8t$a zgNtpHH8ZtM@|CVoBbr|9V*=UMgr{yWi8~|OimQ`;HMOJM(N{5Tci0Yo3Em-dIXa`( zShm;!FG(O!=`t8*Eu$s*jZKfjbjJn_e*bkBR_p4CfJ>~KQrw9r6yt}+3ucYgFbPwK zlMv>g4o5g`qiN+|U|0lPY32xN<`hR+GyR$!r5MRc#<6bF!@w6-atl;d=L1CT-1r-U z-!ee+R{bB`bE9?8jykz)W2rCD95uLdC^ac6EV?+W{!v=`t{O>fQnY65bTprzcNdvU z%BSNi(IDnu7d11?6tq|E&VScMiVG&y7M?2)y0`l^^rq@2x41U4s-~8Y5Am%B?6h<*3-Xuj5q+gEuvHP(t;dF?>B5dO(&vi~ zV&#J^+Tu!y789Z%1iP()$J`ZGk515s&{5k4a={gMKXYMJ4Z=1f7WYkXp~*=eeuKLAlAhup^0=mJms7t^ zCdKlhpxg#Cq_M^*-VVsaAwoU9GN{dZnJPTQ+D5da;is+Y9qPYtmg4i33l+QOG(_U3 z(Cx+Q$9wt_e-yD}EqD z4|NO-y|W7+vI~--PVAykj{&dg7|Bw54X$EE7=D0>jM)|ZdL^+=krAZXgZGKlfTjB` z`n8mJpGV0(-SrnPJF*INt$~1o!!H?hgs4bLkQ_8`%6q}tuoipAgj~{=`JMFF91YmI zm>3~={ncTO@T2{cC=7Z!zU<`x`gG**On2B@D!X)ujuHU@tiYkyT+MnYOZ#U``T4RFtoZN zKK8uPvPn*aF-is+g8FDd+;|_rT)#U>+&*px-2&)Ase2X7u>cW~pn-hC9=b?NTb4O} zO??#EJOku{aFSRW#DI50Y(~*-5jF`@^D@M?=8xpmnVfSZ2c5@Fk`+)P&q91FX^;_b+q-L>c23mKrSlyZ;e^D4s;*dR!M-ZC=z%0Yw2WrlQaZ0LW zijE%IfgaHUugjQ)`AXh2hG6|Qg~~c7g<%hU6m-nn-d+6#OhnhYb+?N{a%*zGCgF4Y z$O_|yX?3Ur^p%`@YhB;bm4eP$qC6e@Aqei$Z}qbX^|RX9Dp;xXQ--o3m!Fh$g&ZLh zaqT#;nPq((jpG;fAp_YX6Ph}7cay&nIO6||DS{!nOm~YE_+`gIG6wezP$V8(1&Zt_ zmJ~j~o;XC%k(Ku=>F3OU`Doiq00#HBnue;o2lUiHjsdMpPV;c%SEvzcaZfFa#5qEn zw8k@Mkh$Y`wL{ce)pjn1fhUq-F%4=v2;iHVDalv}%hYN3l_)z7O7A(2X-uIij9D!<5 zeYkfkB-weF0~ARW`@Z$F#m6nNc4@u6dY*)gMnJK#@-^9$1XsNk2-2m>SVM(ZJHQ~pLgUpL&+CWq z>m>AJWz0h*(xVsd`78U{qg(yu?K)EYyiL%zE1+v3$fR$dczjG;Ct-*;dlcIU>M%2j z9v{NM-uc+TFI##ulcc9_8KhYuzxs*v^PkAHsdRm{2F7gDpU^B(s=EpIsa+LuVC869 zuAq#u-$X_NToO^P;^L9?=paHRQOqS-coJt)$|HL~jWg<_DAOvnN?898^fdJ-$urR@ z;aPmew<@`Bw`Q7WJqywd?$9S(9vc7-{ILzq5S5Te%*JLpMPCxSQyuGrYeHREP)4|A z$A!b$?E5bJP~#|ouJoQ!R)8PzS}MO-{il{cUi9&D^FvFLVRyx-H4_Qp_`%c+=p$0^ zrPhqa&qPj#LxC03*^aWYo6`=%SUl#!Yb!9C55o(7T@N$4m4qQdrPD3QIw-`Nka#fnoTjij${#&O z1>=kU_La;hvvcRTMxBDojabQOi`S$^hkd(HV6n7_#A#uyYofoGw9_#O5aU$s7gZlLJ(V(n6d||pBoW`Wa3VLUh4zFyh*)z7 zqcNGTQ4z+2DRzwQZ)xNq%SRxzWujfFY>q-z?Ptc!*68eMe&9yEvc>eVY|RX9H(qgN z(b@w7EUyzqmUqnPff4Keq*`b2^mPMs-y|BB7}Sj;GW}?^d`L7-65bGoSCHJwx;e1H z%xF7@oH($?h)P{M=@NZv>!Fk_P`)5nGR~qI zV~u5)blWR!84jzLE)^>zi1E3EcM&{n{(*Y23;kGOZ5MNa`nbxD<+MhG20yu=iW!|3 z+KG_h9IAk+j^9B?f+T7tUjEr8YE(&N^cKb!iqn+_hKQ(SvY93CA5IJKysN{tOT^h=wjrJ)-+g=7O`5m&3sS^l99T%|qP>T|EkXV(s%`z_6#yOu7Fmx;m9GWdX*EQ2lHr=iB@X8}H9pFw>p_Z!m4fs#E^b{s8#5J=)F!XT4Awl_rI zwFS1{rm+18%Bk1et^#IVVfpM5MU|3)Bd;?*^2dokK{!>1aR}U# zSN|ki|0KWRp@!jMQ~%^;Xyzsw@080JBGct#VkXjnn5g~Q_YP&~ZQ;7|J<^RbW4eVH z;ged0O&KFhY5{bIkTpcIe4`j+w)p_fwjRDgvu!V+g3y;z#hnz>muI<`&qA!;PQAi; z2ZzWCabEKcxB|tG#@fMd2aoBAn)Oy40W~#rtx_6GIptEYZh&4MT4f5iye^6mX>@g< zlxTGQCH9j6hc~RraChx+M+o<87n^-WTXKc5g0JUcdoNzVS zxQ*=cetm=ZjunGT-BaF+Brh6dVAQjDOTBH6*@4SM%>n;Yh$M*H@QMd2pU1pJZ3em~ z*mqmLMEsD(ShRzzH2;&OQ&cAb&=jj?;(Gl?;nZ1>l{U?CJn!B=9m%#6Oew_UQBGr| zUmTr-qfdYb@2&g)aQ2Sjl}6dtXjN?6ww+XL+qSb~+qP}ns@S$|I~7;urcZZ&eY*R* z=k~cj-aq?!*4jVjT63<6F$PF1m~R8gtMq6$OOlMcG~&$EN19>0;^POp6YRS~MMuqn zsvlYnbCSiFNPivc%kOFAL`Y8Rc>vg|i-pyhwglGkMEyaoi|nUdpGm_+d|xsb5WN^17*Jnc@ozQ{sk%lyVkX46!{cgF^m^^j@8+|gH z3b+302Q9A9Y-&P-u3@0@)-vkHYNHYTTJ33IJG}{_;{<(`j)p<&O!XE%V6D-n3%;eo z`bzc%l4esUAB$IaPxHk%c&hHI*6yINi`HP|C=bXDWGA!WdTRFZ*xVh+#@1iQ^Yc=S zd~+B#k6#v&I&MLcDo*JDb@c3xwp)u~bKrOX^e)S+Ge|6!Z$Np5@X{+uoVClK} zD_aNPxt??FK7-q8Y(KM*(`23U(rt089iH#~-g|;4WNjrN%ASDaf#T)29deUltGOO< zeYa)-);{^06u?1x%>+|n^twP*7|%j{0QE-lNZl6kJ;eV3g)&F6&hS+2fqV|lYo%!- zow1L~KS_KfR9JOO6JsOEE_?4d95CJBrqS8}XjeUI>G|m2_3S=dMFKEP_E3}l%s&@t zO0n4&jLF1+jA9^>ROm2<*(2^VNy@*W>BuAsZAI|{Vx3g|<_2gXqBNn7wufv*mDazn zvSu~taIagp6O1LYN=j%=7jGM2@Sbx~U+Rj8qV%FN(CmXT#Z)Y10HK!iq!JA=4u*lM zTq{ps3$Bai0>M>afu)3e#;5^bgb=1M)=}eAuYL7o&2@9=qB4TBF>I^djm+zx(OEdj zb|YASn}80AW53!G8J8Al^@4={Dwn`Y2^dNAF{xs7c$JQ&64rnb${45!!09QNP^)Y~ z9F$lpjGj0F&?p;pVb*_0_`GOVcT8=;dT$i4ce#(uR?BR$4)Jt(#yZ z-BPe>8XZ-^dE;+l`1Ubmd_E(mA_{aJCGIKqtwe8)yy>D8Z8jm$BDb&%p#F*yHH|yP z2p=|1Q=*sEQ=hkZ@!wPRyRt77rFF@x<_ykI7}GQfZArFFL`2QSVao{ED}KxMZh6u_ z{ygmdPGV`1Uq1u`$|=u>d>JBE(PxscD}YLa*r{3%e;^=oWo;Q0(}eX>P^U|>Hrd< z3WSRIb>$_*7|1b{CP?)hBYYo`t{C3FJ;ti`eguQ+32w#yL9PMeMFJ)v7Fo~xu_X7W zj?LRSAv~xx$QxzEG38X|qmy5?l<{xc>H`wof|DO8g=MCrit0|ddk2qJ;-MIxOE8th z2(VyuN%FK%p?jXzA_AMlf`TdBA*v`CVr^O@W~BxpTKWly(t!)i9zKI|h`UlA=%pGw z!`k&RGb?0*rs?5#D`};#7;F23glM+-dj`0%vfRJGT#%K=B*sHSzHh?*ED`!?N~`<< z!=qndn8}1n9BF_-p+FI!6mV>|OOZGTGwWK1y*qM>UWARN!YJd2nr-_pxNP>-h&SG^ zVD$^^f9HPwj?VfU1Ca8K(ibm_w^|-0qkNU0JSr;UMg<6>JWT;D9|&z8S_Mq5V4DRn z;TcK!zHc!zRMh0D>nFuX7Yi+v2sq!NgJv)H}OWwiOU(FW*N?avt!SRtkmYCQ*k1kZLR3r3=A=R1LK_`2!fLK|}42 zry|b`*H823iO~=G&Lql6W}w{%%>7~1WJ%F5Fi}$S5M{x{ZOvcveJfE636C)<#~JB_ zwUxvODV9{4(k-AA=FSL15K2u4m0GAt81nUXa&*(Isb5yYP=DaHOW%ov zpDE9Xu0_^a7jkOgH5-km;?-V`7J1drpU}l~N1+TdmR(rpD&$46HODdtD?72GHO7N_ zkjtVzW*Fpn{o6$_iKu+SM%TZuD|)uo#cs#83q4j3)m#;R{xE^~prBKVF4Sb?Iui!( zIB(`E>j%ntzq2=d2-xQI=+~b>GNA{HdOOwVcj&cDm=zYi{Kl@7OL#?ysbuv0WcO*5 zt;5lqb;tQO_nkT<)eY4y9U`tEG))~@9$fr}*b%4XhCR*vo08QPt4PhuT_wefiJ)); zuB~_zO4m-oFZ6$Y5J)!*854ZnMp1+>ajXBugMj28q)C5)t^W05R;yX5A&X)7gvhLt z(2R=A<+W(q5<-f`7X+k28N!0e6E`Vab&>#A1;LuKLsYkDdMa6I*;Kdi@bFyG*WtHi z%|BVqoW-7>{YZ@bUK9$#NAfUz)WtpX^I`gu@AJSA$!-OdQA5Mn76_UM~CU@#wCaVkiWe6|sTT$dAJgcB0AFP(Q#QeK5 zye6*F8=Ho9Z*(p!aAoF(tF7wLP<^LZm88T zHHd(F6a(Wl_ia%W?89?bwCv_##TH?lCzrl)LfFtb(G%XU5$n;xX}fbyKh+zGSaCZS zVKMkgVnf=MxV&i{$t_w>m(KZu3CF{Cp@RUq=*&P zEHnUCN}I%nXg}1iNMA7LppDbUnZ;ZY20&|C9CT-W$Htf2CEl3sYpcQ>YaIUgyWx6OIx>PYs zSw1OC6aAk*NKryzD!D?G2U!M1Q0d!h3KvqQMldbQCX3rF^caTSW8=j!Qp!FCutfRS z@?;~RcGxH7Pc8u39`@lCjv`65^{A3E7R2=Ct!j>U^PmY54bH|<&m|+FZs!5KNqO<> z;!GvvV2??JZ)*afJeITNT-pvcg$v}f(CaQ|MwJ*9k|PmIoX5nVSN%>x(ySp} z-Ru4U8CXJEaH9oLJyIXra%c{YIt0s%>^WBU5`Z83R620a5W?*k($eY}T;l%&MNv)x zQJcna&zdBC_#G1Y0%}1mrVcP3T~<9Z?C}_Wsqi42c%~ec%OqS{GJC7r$BP@L3s@^n zL^EkeW1|H9f!ri+EVndY8@hfJ%WGY@uOf=i3S~Js0x7||FF~p05vFvJ=U!o>FD`#0 z=eAq&Mn9gL9I2@8+QZ1h7M6f}eDltJOfvA{80**5LHd`^H zsNR86hgc$c9;IYR4TA@6hZq}Z+*^~D<(Xc8Wf$j3xT5v<-Z>_aY7B{CmHI1H!vEh>ZI*H*Z0Sdtes|zW&;eAhaVh9918I z)_dw{Me1HSrCAhpyh~2VC<%#g)P;Jf(qy2`kHTH_$Y#s}$YX~^RXb8+kEB;+_@1}P z(phNMLn|chr}00-6;Hp%-^q)PYfG_7_pv+BcMlP-;f=U1E+-s*seFAw=Iy!t-}(MI z^n68||HJl4`0t^|#KyqF+Qr1d!qmdZ;4hq7|3<0RsVjyG1o!RRAk7#0`9Im8Xc(#->^B zfx}&B&1FlfgAwnl3e-Rm2*M)e)z#}F)i%j=mRp+5nl{xo)sLG$=o?9p$q4&yE}oNn zJ)gIZHXA?8+EcmThYIDsl@&OUGwCbotL^cV5&8zVNIJf=_=b8)Y(Q-pAu*X@`3z)< z)Fmge{fG~?29-tSl!qi0?Zu4&`4xw$Uj9CCU55!_f$NdITphX@4T%q877Ebh30fm{ z_EufC3hAk6vbCK>KV$f4_ECaww!-U5HBJr)gdi_Y-$cDj4=I0V?J3>ZBH1>rR(sAA z?aU8N!89!unF`lFgnku6Y)a^81H+n%+q)^lQl25eM90F3W`u%LLD|#qf8>S!MK^U@ zJufs_B*q-=x5K0N$U^>z5TTL!yCW0CpQ})ZJ(^C{-jNR#UV`s8Nd%U8JP>-mJw?(l zM{W;!28F%=-z;<#C}gt4S>ig?x7)bB`J!9$gEEmCij#k+g9N_tkO4Mwx+OX4(#ekQZ*U ziaLvqR5&^C$SGbTA|zv4)m{9I^@pSF`rEEan_#Do$GqUj^CnQU#MRT-LWT_am4htx z#8-Z9b!CyYJxj+&9U}~nb@g|UBQZ?^Tno)lOD%nD^CrUA>cYjMBiz$w@Ge(o7*AX> zJ<@h5EV8MIdL`7$@>GY2XiN>gUn0{&=r47ta)LJ5>KiB#P{ZX(D2jkAd9YAu6zrK~ zCB_rGpdBaqWlGU1;DVa~0bNB+&7!w+Wd)E@oY7z#svaAqjzlyT)fU-WT3YK|YEkq` zcPf2F128m;Gl)DBDoy(=Y!vT2R&&-+lS}wfU?tqYY_=BY?b!D z0}Dc)eZ8}@8R+8^^g9*!s0-eCJeK%>Q$AaqcyDT)9ycY>A2^kT`zVq zeNdbd(cm%ND~&X0`g-<;e+z(`RzyVl&OC>;WuSDjCCaET2y;v(8}ch;#w;lNwhyJ9 zSg;I7IYTc=aAT@JcShA7ZgrGy>H?QySC^;1-I(Ltumv?s>=q{7bkqJsB!GAB2Y-mf z?cQ7)|4?vSn2zB5c?vZ|r5FQfv0Y%3fVssg)QFy%J!H24hf^<;Bt^8{hMi1W&V+%6 zN?&o1QV5n8vc=Myng#E`#!5h?!Gx*pYe@uaTY!*S5Ubk6U1462FsN5lo8(Zd9VPfPNcSZq2qIz_O-=>DSHuDU5 z0EZ-F$V3_RY@!s>yR6Gom)nyXwmlR1MF;ybZ7PTH8xT@puG%>Bd$({3l-m-gSfamD zz&0tNI0=)rtUq~BpY?&M*@|lSZ7RKaA&qO20>GJk;FBzGkS{moX2rPCO4k)AcE6)y z-w~jPAo?o4y(wN1n(WYUtlgU|-ZdEds1K@&hxtslL*>csb0tUd#Dqj zZv_rD){uM;V>&k~)}MGMfM5LOGhIU9ED=~6gEXiq$oqBoa>c~T4$A^}4u;_Ir7eQI z2#-`iA%(AepYrnfW1O{xY?>&T^!GuP-ITMNCP)_kQ~P6>SSy8Yj>4VMN6^*&l6OZr zym^M5Was7spFyf={`iS9cWmxBaVm{=fM{5yy(UT0ts`nrFZBL50_Zpr#1mLj{1Q{9 zinCPZ4Z62DN-%Lpz+Ap7)ADa4?>_Nm}$%~SiuW?$)w-K44 zTm!Z2!X4|6@`-JM$iarag|8XSafIE~vYl%d45P6!dgaf7HmLb5SJ~u+9TM=F24|$@ zw)C$MIM-eD5>_eOep93SXHucYLjt-UOg5bq86ryw5k}L3(Iv+k8nvLbq{o@wA2AG8 zZax#=`G(;yYIvKkpB{YV`rd;Y4kxng#<}GBC zI3dV1CohGt@*jw3QPPN<+-RImv3C|aHo7*Fg5f`oC<>te_-f*Fy*`Qz+&y^sgMnO|?bwOb zhbLG4Lp`zpo92*ZZ(%^`9LPU8ezzN!*1|x^ReqoGmG>wl)O$Q2hn)Lin zoT2y=xvC(Cm%N)|Srmi8j#9g7xU49nC^7K99G>{cF&LlT9zAVO<8*Y}>Go0sh9odfvgeQ38F0BXrou|kN2Bh^H=O@FVSAuH}Oh_+8)e<;-C1~c6vcWlMdwJI2B zOJ9N^O!?~^hnwkPpE`<|N#}ZzN5Or5b|x>^G)w>Nk~Q__Tyq z?lt?&>*H$s5^4oU*{gqon`B}K1)(xRg0hbxO&eHLw7cRx&!ocOOz^M~bx)z>JyTNFs_9ttkf$Hl1Y&CpsQ|+q3c3^Y6C2*tA`eu66Y0a}vANyQ8rhihw z$sS(zO2DyrU(sWhNEfseJ*yoy!}|QrRIF+ZxR6PTsR6k=#X(IK{-d7@6swZvCvc^; z9c3nUBm3pA#yTrI{OghSl(&>dCVw#1B_!45F^o`Cun{I{5ojS3PSZV>#a$=WbIgz8 zJ2~v!dmavKUF`su5>tKL=gly>&2ZG!Z+uGdyWg`&r|hsQZIEjE_3jue9jNFA$TuAr zWPp7Kb~M%|>?_*5{rj8X21+-_ks^lpZqr%j$)Gt4nnkfBnu6M`=e3pM83FV)h&}H~ zmAko6=SMrf4|)+kemf_UjuA#qwVa?Nbb}{oK=fv^)?Ik2wuE(Ah@&3dzK&hJdau0P zRhf{v6J00uTr;TwsrJ6Lp(caeG{k)a!WWR?J;HhyB3q&AQ&BaE(z?d^0Js@NH!o6V zlSSxAcIBkxo8BQd<#nAWGmsZm<~F*GqXyfdqvV)~2&U8l6MA11HJ~4MGGIA_>r=_; zHIvmZ*GlSLcoo*X;Y*!W-Sv0f^_%1%bOxolLsi0{IsV!{y5s#i(@`}BCfbnbT5V}I z@&Q)v$1W}-cr?ebnYR?vgR)Nh_T@>zlYelejZ{8DMGOQCRdp@O`}@+~lr`7J)IDMK zZflG%Vm&1WqmhQAd;NqeF3nea5PD<>*?4vRZRv-Jl>+j3m(4VMw@xSyPfjr)JesNb z$`412crUa>BwibyBXK9Hbw*K(bOmh}v=Ay0mFe*J1q-j7K85GY#QFrmz4ejrQ z8X7&NoeoEDlFz12uysWmairDG$V;U!B9uKokHM7ZVp5rxW zX*Fx*x0KnIp$cwy70=|*GhNWvW6+aBXrdrDQM8yTNY5%zf^>xr2>J@dCIxI;iS5Yf z83^c^2t1QOVF`_QgKl9UKcY>fB`RS&yko~<&roX7l*W)J+aqPyG6%D_ecIr;Cys0z zM)f4dY2Pc7q>eEjNyprAauT*vj3SJY{z$jr<{&^|`?WITDShOuo< zbmTCKmsA2CZ$0Xe<&T1*1W^_;V-DY_YsCC~ge3hfRI->$s*EG&ICaZ@JnpG|9#bL* zr-`a*dqYlIyiMjFjT4&bPB>*vmEg(aIF8!1DmNL@e$6kt4hO1a*iqf9B3RGowj?w8 zI^+w@D*YB*zSU&@H!-+rQ>{B1^7aeBFbAC7kK0C=N3C9oHBF-(e{^Qd0QY1K!xU;M z*|2J@4I(p}ky^G!YqdSqO+?`Sa%-PmuCULQ$q`uXjp`GuT~U{@mX&!c-}c41r_|C>a^) zGGg+SGJzL8|D2e{Zc(=De6`i_zIf!Q|C7A(FI0YiO(mn_CnSLxQ9?eYORJSUpLOZW z2wGIKz{?+!$$BZLtj|w2@zR2Pwr4{Ey*qQ7c?q@i6ajkxlC_=iS&&STg35shWrch92;RBRu)X?iD!Qb&mHLBP514-V5M zP;-)V}t(-_9^U;L=>AV29$6MFZA0xaL*w^b4%6()8g+>>CaZ89O@E*_j8Bn!oM zb%ljQS9uc0S(TA_ zmU>PP%j6xrjXkz-MezZ)$el%`#hqJZZ>dGqxvGE3UTK8ZZB-QUhx+{ZbrtM{`gdzn z>#(V)_&}9xbR61hjTH^;*4=2^f3}jEap>FhS1-xrtCh|DpIGVNvVY|->dU_su2pQ5 zuvJie;Dl4GQ)f%i5bD1$QxdF^X!#gk`Gkx51d;^VJ`O2%$hA5y%$?YxpZ=e{dUKfB z^%f)3P9MvdzDWn|6N(EYaQO$JTb<9BJTsg#j?-PwwLPD1-w8s&xk4DK2u*isqYzSx zt=_6jZh@ZT?Ytc2UH}u7;WB67Io#HuHLi_B*&(z*E1d5V7#gWDFude2rSnT1qKGSb z*li$zb@QNX*t8sZxCEjSda+s(CCc@!sxIN8sYJXg|6DJaTm4OYg8iN44<{7MhVIdH26at!(`K>bV zNfY(_W!fa@cSbTS$w)p)s%snOY_41S6E>V;!Hqp!u3NM!#V7_9p*E|wXB9|H zM!Rgh`u-oX{<cMsKPMD7&aQDQjWBb~ z)fwv|I)l<{P%LYdQem0#;er^it2|Tar;n{Z^Ickhp+=#%J8a7J;no9onQ!}8U2}j9 za*UnacU!Qiv%{Y|+Iu$#Bl5^=S8#a}JmF*Y&dn;8&H91xv z1y{_?edFG{Z(Z+f4KM9vz#xo%!$A4~@1DT=-Wr1W;}#Ut=fQi(D5~cLW)BrKC)vH^ zW0+Y^0Mi4A*6|&3*asT7*dmpSV?@tB=0NL`5vF_#w-5Ig3)O||;YU-d_$|W|jO)6(XBCZ&*Bu8xO_OG?@U3&%l}Yj{Exi3$=^x*|E=Jt)a!DE z{}mW6zdp79#O3^BtABGo5&#ziYYSs#TPqX5zmmRw2@nB&4PgO8pGE}aq1$eMh+o3{ zxd})!2p1AOHz-%r(XL$~eMa7ixiQ`8bH5iZo>f4K!RwZI+iqv3yKM96>VC0bV;1-s zf(oFLCP7YVai$Wb9BVq=5VJb2V+pobN4tau?N48q1(5c)w-8O4QrSPrrYo0~FWzaB zzDbJBX9m4BN7l?lqdD*}r`G4dbUEis@t8zEjwjuj+=TIy2Q9_0mM0EJ8)ucz51g!R z6n1_iJY5YZT6|kCTd?L*9g~kSGR5-7w#OY?!uX8y@0_DFnEiYn(+aZFZzNG1S$AFk z%)ILLWo=Q|gZ0a83^ob!s$rnY8)v`&vDY>nHOeTz?g_?olz=tjAj_t3qW&9AA2<4^ zKC_yQ*Y|%814}#;nFL?H{tU-)-XvQp`3{DVNS zxXOS)dHE5Lpac*^SbCqPk3nIuf2_+1*Yu$}08x=Z!SymyaDeS!0U@EHPOUQAZm(wi zm$(A1wFgC>x=48l6IIZ1b&dt9rDwIr^LIJq1DbpX{VEAK$f2hiy&@YHAerpprx8d`vRN& z3x=cn7n?t~-RATV#WmQB`MeA%GYeyJg;G2)A{KDk1e}`lLB5g2hr@$Ge|CF#HwoR) z>7s#n8P|aSBCXKKTOVDq2!qg42O~F)Y5=-;D5`N6U{z3-1$@*+K5GOhhgNl{4-V14 zn(%XGdFWNWUVj1khb;)ywA8tDdv{E~9!)4;^>^rwW@ni9H;AUV=iK41H3XpmxX)sKV(6nPHI z9or?8sV>{b#Vq>IH@y$YkMFq(@xsX7EO%n%W+_mDBuGalX459!58h@cldU)Se86e` ziC?7Q(3se`r^Xc4_a~co?Uo$yD{R4)m|SMX32%5p9@am%gK0Ww4y~OAOxtLI$s@Fg z7W-1<8i6fG zOGew{at&UH@}_N;*KVhmqv13vds-hY?43o%ujYMkMIzJlnELH?0`UyeW?Tq=g7GHm zE-XL`j5;+DR{Pp-q+f|PUUXNNzNDuC-_HQycq+yfDgdK_(PIA{9rHeP%#qihDTxPh zOx_LLOpRs5KItP&C8SS`u^bT55k-<*O@%7IiC}N??sr#!5 zJt62>-gAq#0a+aS0OrAL4iXv}#`Uuc8)i*o#dby?KpG~bMk7w_ao%)ZGW9vE($Pwp z@x~HiO4Vx@UL~tZzo5y2{5$goN*;qDHR}>(4c94W4@;wVDpBCV+yuUgsUEtLsXa2M zl>$(x><)iH-NBdtxrR6GyC!iSJ=-b`L9ht3=Y%LrFFNL%-94PS8}AG(?((iZIn5xm zM|LHp&vy#ZrTUc^qEQ`A7%~}Hxot_x#w92|0fV&|m~0ay`h@Pw5*ZZH!mzl4)c{kz z^G;EfB-}pA{o??Wd>!B~|M7oXQt1EgOdP-T;{I=qsQyFhC^{C!o-W8x@ov^xOOnB*hURfIvNP z9S5^^S=HGXzgY3BY6-8UxmrwXL$f#)tq_*7Nz>EZ3f^<)*#zEm{bJ#M&SxicZA#Py z_;`WOWqZ=IXXkxuXUFq>GI9hDybk6Qt`LY9)(7`H2xtX7)^AZ3i=vJ~t!I3egBX6h z_T+{vRG{v+kQ&J#`EtT4BOoXL^c38w<9XBJde13kwhY!rjR{#1c-${McC|D1SLs@N(|^H|-(U`FPcGQy#CWjGR;9L+ov4plKO()Nq@lDb zB^_Qitzc+4j=cgsNS^8lhCTWw#=OqIy6?0E%}SeS^=oUD8O28#sSjHhT98ailcF7X z!J?n42S9_SBtfPl#y;4TW<}NDx#un{wG=jbTZQtApa-_Gu<^QhoDfYe&r;dBl)JT( zPVq7y38y7ZIBUa{Y-o6k*0wLp4#AqG9zEXDUVO<{ZfkjXFwz}$4XGC!H9rc8s6|Gt zYT{nZ4qr}cZc$R2X$_yV<2W{s8F!pEfm~4%QUdwI&c{UqWy)HmE-(=U$t-%=I$|(d z&&}o=QMW`_tb0X(HJlY4CDnw6Oh_j0!}*ugPq@SR+Jx?#t}CGS-{;KcoUgQp^@Oj0|B&;%(U^X$&wgraoz0a1uP}CrKag;cLf8-Gs-}I=i?% zNhvyi5mE#Fs0fANCa0=W5#5#wj4BPvy?0TgJ`xku{17kym55R6DrIK7pu+SLcFZK8QD(w2f* z$y4Mkoili2-q|rLk{mgCD0XT>Z320wP3ztv$T#-C;Py%!RLff&q%=(FLhY$_Yxmf2 zTMuB592=zJ>e6y4IQ4nc2#%z%n&Qq(R*q{yIQa zLkid)Z%cMj%Xi-%bNi(kj0z- zl+Jw|bNLPuhj_b~=jHhjy2_IjQ=uf52KlzO8969df|1(9EIa?6!Ih!#>4us5wOZ2B}Wc(9b4(3c+P|Q+!P?XfTlO(k?P=F2nlF6v}%g1hL z0P$I~?ttA*cEI*% z?T;&vqo`A(;&0_)rMa%wD)#-DY=fE|73(0eQ$WX@IB9~Zi|k~Jr{h`udoRUsHn2q_ z$g2sd>;k!M{jxaO584r9_D|poewIjtKch+%dKsXJT|7hT)iPKbTq(RXo8N!!V}HB5 zGb!g+oO!_dL&xykWW9VVN3^wCbU8}LXo~!Y+D)p8U;PhgUu)e@SjR=TBPfE=6MG*K z&M!fU;X;I1iV(SbOy$JJJ4#;X)Nio|kj+yCKwmmb<*Y^s>kI03ILNN}Lq${3@#o^k z5N2$mB_bu2_M*lmIHB_FQJk@Y*un>tr|2jP?2OoV6skxGX=QV+=57NhI+2QA3mA`5*8@cG#m>tG^>H zg2X7`<4|HSCU1k#L-Nv$prDD4K~u)rXHSF~xCP#(P4+ z9&koQ)ab{#FYb5Gr>Wek)y^S!LOLSM+9B$((x}d0P;K6kVlJfCzHTb)TA*6+b^Dp! zGO*SrfkUn^qCP81LLi{wE?`*T_aCb-9Q3_2+;Ozr#5my%k;<{WYMxdSYJ0F(#2VdP zlW~dR3{}RI1rIAgQ!n+~V@w8ftc1p62tFftv`)rKV+Z_hYPA;yT*|`so#_YQbYLfP zXj3O1&wFw=TP*WOWYTXO1BGuQTdHA-;?CRO_z`~+awTEi}bWdEfk@S3hG8=d@b_Yb?rZh`!dY8ftuNEOP8m7AKyh2EB zc#5vWQIz0BkGu~mSv8azPA(04UBV!$?O%(82J#dx=@q@CBJes>7~;e$=?#+jdR#iW zaqCRx=9ggG%RY)-R8`!jboE?R@lb)BRU8ytf~5Y^EeLd$1C8JoUN)k+t)<+mt8rrXJ6MA;-0n88 z?p`(w$XG)P{x<~Q)9kfFxF6058{C0;)o4eF;YG&%^3l7nI^&k;^R}*^I$*z8$Zz>0 zXg#u^E(qhp>ocv=4z>*C60(25U7_iY)0iu@CzsNnZ-k!vH+Z`L#+{Upiv1t8!`Chk z-dH~Sn)m?p2i1=g6;{bzJ%ZY7!jI$wf{~u`%irLCf-^qMT(BO11w|8)y9g ze6Nyxy;=VyqfS=Xko_ud@FY+4M+6YKMTdvOQ_cPU218$l*supWcoG1MwnAsoc^-Ni z`eM8vh`%d_X@-t0AuVk^Kb?M-&b%=FU;1faEjly=M+7?jU>YIgK|7B0yOx*PJTZfd z#aQfcU~mpF&X^u#8BjI@TKQ3CL5xY4GI&)h?dLahIZ8B` z`K0h*f+Ix}Pn5tH3^dJxCNyd;y@BG!3|diyTTr#2LUXB-PKBNMv);Tg6pBv+h-Y?+ z#GgOXTG;0qa9%%8%lqPxG)E$Oaz=Nb`o&`h+oO&glU#8UAzXs%(evx!*f3oF)QrfR zQDd*3~=Ex0s z3x&(rj0`}J4zU}{^pjZ>%g8uct4Lk4&k4&<5Vi&X{EKEpXEaY~^OtQ5zfy3P|HwY_ z4kq#j4vr@O7~{@X{=33*zDc}ob6!}4ddsYcys!WJ=_%p^n`U|=hqB! zcX>G0k$;?dz|9|uU$TS*ElWWxozK(luGj5mE&$(;j}CZ!sApsd!bwC%1d*tG8y#!N z2}H{!O?8dbAwNPk8|oizGYGP^DS1d$qkP{u0m8h_r*6GH4V#ppvwoP6Tpv? zHs~gt1a***A;(qgNHIF+rQ$$1A*NOar?OM{4@bZ=s+CGY!5o@%!*hVVues#(&qi7g z2L-q8$%<4u)}Le|5*{$P%}dT#0AMp`16{OQT(u+?mu-GUBl{g)6{0dX#lkKyx?Lg@ zfP|bQlk_zEsVGY1A+6PH2Imtvl%v$^rC^Pu*BE~f8KmthX~?0X24AbNYSYgwR zXk3Lih9z`}!Jcafhg}w??>n^6{;6PMlJIu5o5<_z@_k0xP1H6&K=!SVcm0*J>-yEOiZsn4i~%Z zhDl)FB8q?tGa?xt3pr{C6`n0ujDbVfz~2P{UvpzIh%qoD6n#dI!sgheP4@RfO=qf0 zvib?u@OxPwKV}bj{5y@AA8S2s1n$}YIg!sx?YA!aa#Ivv4Wh5<&~FyD^p4JUCJs)2 zDYrV(|2>iaOGf_XqWzok zMdSm}S$W+3Ox}pxpScf2u{nc3jZK)&@fRc%JZeifGquezvo-bM+x!HQAL_(WOtXt~ zVGM`KXNXHx1Yx?2RCUe!nHKkY8tU^A&Y-Y;Zp;{56_^!?ZU>p!jb`eWU!a~_J_}J8 zb>~b0rdH3v4Y0k{f|q0MYmZy;PvPlT99TFhsCDL(FR*<%5MqlpRFUGlB8mk{)vg## zKy<-2GA=q{wA?6q`9QaI>h|KPXw6XRw|}KSo5EJc=78`T=m|_pQ=NR(E8fKrtJu^5g_ufg7v-cT^2{BMgf8ozu?ZfWBU+@BH z#S5XT0G+=@WOpXn)epYibMLv)kFI<0ZLZ?37{;367@A1!kJ2F$Wp>%U49}H_X$!^4 zUY#<|t@Q#|X|df?-rm_il2POuaY3_aakajQAA*-YAlJ1w{Z$Y1;08-FIxZhj6<4ey zu8yW4?FA_XsTWbJ+OK7*Wt#GDLSAbLL@Y(GHq9W!ybg7?iiV0A2=_Vjh2ek{C1u7om+{c!`9JV*a_4?1@qLv%CR- zDP-432_#6h|x&_vyhY$X#G5S5E^T5fA+aK*nH?F z*US01_YeJ+r~P9b7KjDc8f>-CfA-mW$Gy8Dj($^}t@Y0(zYtEJy#oVIl$_|SXspH| z7!hC(zIy0YpFEv4%aoYO)fJ>WM1y8b%4kaAYSsci(<*+W);m2fH= zjBH+a$FgEZj2fsB;5pRp5Pl6MDGIzO@cwQ)AuA2s0k>p6gcxu&5a79?zL)WJH3i)M>!lS7Yc;q#38?ALM8rmzQp1#`% zaiAY6$}w!O`^s6UkgvdV4!Cc`i+E5jK$X})Q`~~mgqDD&g7$VYq+CP`$aiYwYAzUl zjBWq43VDjzQ|P7PGwUi@z-Xv|bTVF$Kw;vndMgu_#!)3a=y*B~_y7WK2o?iO2yv9` zPN_T7Qui`{Qx)48bU-GA!V*E&dN{=4nsdL_+c7eE?Epkjx>#7uraXk$-LM$pi>O&% zZqDMP-Ck6m(XKkKC^iFF1cJk%Jnfx+ne|?O#aU2k8U1A}OS+yzxQfz`q=yGmu%v!_ zh-tUMEc|@m0<-%HH?t9JhLm?x#6QU5vwI*xiSXdwvT6JQ745~mDAslZBpeE%i7>Z< z?h(RLG}DM2twZP$goosXYV~Vr#qA`Ra*X;+SrH6%W3ICLnk1XMkq?LplxU^tXKTow zPAoe5`2mnFRDud-Yb#3%CVft2iv}UznGMkT8F@Tm1k8k6*_6twz!R6IbmSQu9W=HQ z+>?*!(qK!^cD$5q%+<~BB)j=W=-v)_E3b-gB_mZx~m_qUSP^X!GaDtXNl)}Bg_d} z=9LAY6S@&1HK^E||I7_6bn>XSbX9P+^(Xz%cb33G@$;lPk@e^f#+J_GIZ1?DBaE?w&8!i6Lxi(2K)z*%sy!gpj+S5F+OkE6 z$Zh)x)s}Pe^Sgmn>B5C&v5tc%mN)($0CTx^;5kgjqxy8yj`+i`^;3weBxlV!a<%s7 znxB?FEGEPpzYzgW7!p3^QuFV+#p}Hv;ayHA>W`nJep-7YMYQ5b_OY0A;z|i_M#${* z{Wb1(B9$!3wS4nr!-V4-MGf<9t$kd$K9;>6FPQy@1K2S?1Kl$G3K-$#dh4Y;eq=sl zBX+;4l+ELL!T>P9T`}y)H$^h^RYC*95iVSGQUcuu3y8fO_}F*Rttm_@9Wz9_O(=Rd zU24B)?(rU_koU8)YxoMIRHnG5D3r0don&X;vp=q2p5Y+=+X;FqZdB7S?(a>wC!m*a z*C^>OPSeB5qfiVG}6=m8wr2;snbezCeO z2^`o1>hL9?Il(syWn%Q2#~~*amj%9Va5hUXx6xwjqI)#@5aL-O)D&+`R5l z4(LyP-CR%14CoIC!#G7ZW$w_&vXxcUI;vV~3ThI^ne zOT-9xHluTjqn|roz~H<5{Yf)7YCkfUE$G29iNocqfR&rn%ZtOlU@?%>&43QZgW&}i zA~|)RemT_(>R2s&|Em|!3KL8OrRG=$QS5}o(rfKT5TML*B zIR&|gcHJ*>jyKK*l7gk|+Z!Ln>xT~~z7cJ1@eDn}sEB!N{Xd+&Q;;W7wC-89ZQHhO z+qP}n>aty3c2$@8FWa_V)n!c2J!fv*b7t;5+=z_Wnfb6IANGp;*52#;t?}~gCM!2S z0%H%(N3wHZcIkH&`sjl}7Cg&6Xs&#P5T2G-5e?j`p*RsKKs1zT*9yNh8sF?HPR#4) zuX(F~hVON+;$X^q@Du~q45kLVnuOzjV*6;-sk99hup-98)M;mj;d74$N_kjSn!vLq zc~W9dEqODGMIspIeup;ocez)~uZS)6>sX6WqP`J1Wxac*0^#Hx5~g=I#%EY^P7aP;$IV~kokbF+!;$R@wl^a_ zQ~{msXB`I`b3<>6;>%6^sbCaEphwwSl%*G$A#~G=aEikt#UP5zlq!r{t1B}$7q3jF zn|hrnPjEwcB94=~)jX!D0P5`60h1enGTL9?QMNxuM*T&46pji+VZ0(i90!=VynlPZ(!bxz}LlsQO4uu@YwnIZ|0N}`ZLa2$3X z8PVP}9gqjUGkT}@#78}?eCS0mgC4G#z(j@-X8GiFo!j<{XW*VaJbSA>V2W>6?~&tc zlB(f78!7M+8oPmC7dz=(hhT@h6ow5m8duQZMPs6{;A*IV&vqxApXWdrgkh3CDV9}e z2P4r#I4`XhBznABef^1M!2~lH{O)f@cD#O&xkTc)6FlK}oBeyD{fu(C&5J2Hk!VEv z$-*(%7wR;c+|@2@M(I4~rB$P`a6_dzh`km`;P>otIf%R+NO*Tx* z43B&7^hU8++AS}5wORVeiutgo1ICf<&iBqEs{;nIB5m3;;(i4x0>XXGQy762ACFwkL(G+22>&jOw z7wIj~SMZ0-`n7-3V{#F+vl%R8=%N!qq196f))AL4_l1 z+4J}8t~b6XiQH`~i0sVvFz=m%#NI6O$unGVWKvaD=nIw?qeD5pIOwTzpm;TGmJ+Iu zm_gMD62ml}=w(m7Lb{Tr8P^M(JCB1jSdIB8mFfnbVKeS6XnebCU+^aPw6&B93l%OV zweR>Khu!KuFsP@CfTah(i4EJ@u?v= zajzb!Z7D-%dZ~-3VIu7-X(njM=(G@zHiEpKjTO zbx_7VI=&(M9ec0V*!5PppGS%hd+@UJ%|}FVAAnQ3Jgka@e`Hl;iokEOzx_V($&QZ% zF{q|=-qNgJA%X{gw+wVjNP0>ukmHVD0iVD~f@}!53Kc^lbdeMf^@!mXw?or{#Pxs* zwH|kLUomq@z-*>SKi~K0s+xc$822aPXYSq_v_-AzXRatOc#zl*-B=wJcY?6>2Q(i$CPr!UMbhOCUD|hp7^Q+lEZPmwi7X(yH z5=ew5^#`qvSMG-sHQ~3F1~FU_^8hdEm24#0RfH2%9v-)9_x$LRhzZT*Us?>?EQflQ zJSzHSb0|*6zgFcfN|h1#Qr5Vo6UMC$0fT##O^Hw_4PtN{!${0&N0v5C&r2E#^8(%BggzZCrUIRE5=l4pSPU@k%!5dzBg)%9M+b$W z#8|{EfFW0q`(Ma{KSag4aTM{I+xJw+lk*2y`gpcc^K@NZlXBC1F>j{98wsas5D`=% z?tiGtkN-Y*Nb|+0oapQ5?u{9OS?+0~+#Ps{#+kbllhA5;WM{Yh@}qDjc|(IP7$<}tHd`WX$VOXsUJ~_!SDx_%bmD6(ern&wI`3p~jUc6Z z8|J>S`#R@!>qTr1qlv0TRh1ie5IH%(!mh+L{L}O%K8|Wu6e*XvzjqG%MJ>eX%W;cU zriglQVkJR)DZ&o_sE%7m5`+THKhP%QzvKw+FBB9ys+1}AH8`8~**Aib0i}2(yXiRu6?XPg- z<%wW1zwenQXkVbkB9A6P>W`gjt=Speqfw-J8oM5t!9@QS3E)%+n$2`h$GJ=50^PqL zE#TPMd@7`=c!dCAa2+CRoI;uecTVPC!GjrPrG&CPQA)I8a%v*#Y(9lDM8+edNkM}- zX9`MG^MJm!4kyMLk(mij#k{1}K5-Ozp5Nmgv~p)bjpf zBRVPDrh`ayV-~q1WaMZJwH#*Q0(EVP3of~fOb&NfOoo%dg7PP=gE-&Z{`y$k@v4ai z!Z6Fe3;%#?M>?Xzn93}L9uaeyNx!S3*a=f#FX)7%W7(OsY3O%VM?OX_l(Ead9FUfU zh9m}W8ej@&ieKW7#$u9IE!2_KflwKZM}$IgxAE>G9MW73F$jfs!~YAh2W%k#u6+iSj#|uG*bqpDL+V|X$nT8r-zTRM=4E` ztY@JMV)=3)eqFFOeb@?xUEIuEAT7*Tbxe@D%uj?gi0At#K0qKc2ggECT9#9zWTR)XPZF{spw*i6?O<#4G|e;6m4NkQY1}wHjl?9X*&3)6oab?=RHgZ`Rm-} z5GeF@8I5NjaJO(!c1LDamE>=slk0?tD|E4!OaHl2J06T!EO-(2!L6Z@L-_A{! zIgWH55IW3+CO;DIDRr&vZ339NG5MyVTOL^4;_?$VYMyLt)Ngd1eJP6a!l|%E)TL5d zr@vEj1qhp|*O-jJU@wR@aDEi~m5Yk@mizQMs+BMGD4QY|y!Yv~kQT&xyeb2BbntaR zS!G6y8HF%!-3ejN9nkc<3&!lFrya|6#MALTMN1MSyI+3-P?t&NUzg_-yEqn5?4hQh zn!HUb69D(fDdu7XI(!(3@7c@XHiVI{UAmv5el@X*aG7)h`ifezBWElT*)!oTcUfAgSlSCqZ&wfRqk+NGPH?fg~OdO%wAD^j9Q7VA{~jLn3~ zAiln4ZGYACD~kQWGAUS7+_9_x{3b_U{Om=K&NF8x5A?mL-nE>?ou)?oz8Y{^)Wj3g z@&c<<;~|tLjEry?n-3#?jzN64Y0KY22~^h;igms-aTEPi?q0;4_*i5AXLHv{FT&R0 zm#g44Wt=1AgJe@yrWL%{gE?(&T%wV$jOmeTZ0_Pbb zE|(NVEiOWe-r*fpB>OMIgMi_lK3r@h)$KY}i|mKh92s@g!Ao-ek>o3}CAE4A^&~IZ zxPl`kf&{xONDlfuFcO*<*_{Xqv_mjr+yM3>L=WRi6u@tCiO~`-h*%e{!Kek(pHs|5 z%=UYg+$+ro3XP4CmEq^8g?D?;=~=hkg?^Led&XyS%JAcYd>10%9~MO{Tn9c0LU1LH zH6qM}{4(mzwPAr@Mcg_7aW1-I*YZ=)=1GYa`|Gz!%<*i$uTfw&@ejO_`B%=bo&{%QgG>amnv2eka(odrLuVFED10J zS#hLGjR=U)%AtSbqF8=9UjMF8{IOyP#R4*N#Kr5ukLj5yzE(=-4d9j2gZ1Zs*aUTA z30l=YaG!6dzGi|qhE=nV?6i$)ZyVx!*VSA=sIc90_tx|67v_Jww(Vz%+ydix`V$+& zKZEkrvAIX*nErX-?A_EB+y_51s*;GypWxR6pWOoikI8u8u`KiBYximHP912FS>HZ1 z0wsn=*msV7c(>HI700m7qynP4dOc>KA;v)?_(y8VQhF~sk^ zI~U;r<_u92#oG|RWDh43YE^_=50Z7yhYQ{z2ZHRjRcZ9nw1{>eMqtXz0#rq_ zSJY3$Rn|~umcTNlp9+N%vxe4PoNoQ><;t|TD3uji8KHzCT)6Rom28PQzWx_ zj|n3|`>{mJeHYczK}r`^|CUnu&pAB-{Hk%JS)>iE06!hEScA(}Z@bMs%7>B;cn-Tr zd4n#%%pM>`41uDbZ-;UD!Kx1FExN=z)mVIiUy;;i<7SZf0%Q+8;R$^}2B~mn72bigD^aa( zW2iF_mQ*`!<`rh?72_^A|;0eoy<_A{p^y50T};tFiIY##gOJRPaY19 zA!~D2L|OC^K@r>skKUB4B3V)Hq$LTSKB$nn`-;QpxA7Ef+QYqQJl@>go6YG#uy)|0 zds{8Yq!It#=Ndfs!jdw{xMz)pz^#;nUjZSdd*d8T2_!AO!fg8WH-bX;a%8p?jDVP< zfk9dd5DtBw35wA9s5rLNLPsmPF@qA~&-yvCRbUFnEKDdeB50*6$8(z+GuvJZye(m- z0CBP#wcvgN%OA~{yLD)_>~D4MaHcwO%_svJ>h_mX*5#)Y^t%Uk+``F8k+LdKWqaxL z#^aU5SXF8EOV$PPN@(lY`8pzaDPi!C1Y@``?QNL2m=N&s35 zk!&|@gg_iZ&#Bs8i!db&{t6@1_QQIpsD6RASnrjh&sL_6?2Pro-K1)2h>9(hLv(^S~%QTT!i=hRBr)riB38fQMnkO$~CTG}do>HQYo_U&Z05ffJ!d#qBF(RsJ2pM#ehjo1gkSb%`i^X zo?g@;UkB9Xo{k=rtuwOSAo(3uKJ+ZIA3?@Yjus-JG3Ygw(9U0&jb(hTC{GjSo$$kZ z2R`(`gyuVfni1c7xo*UaeT}%iueF%#S9(97`hLR$_E(~vIK)NOM5XKcJ90lXLjd58 ze=FYC88ILv@le1|&g!+)>A+D8Lx2=wuelh{5JUd8yaQ(!qmRf7@mrfO{5Dn-x;KI_ zhV#zxVD>dSEs$|;W?UMhJvRR63T?2844N|;>KzVeT>nof9bf#>XCB$U%W!lUXZimN10klm+1fLTG$3^7O`R zJ9P!TUs|IZ=GLKZ2Qb5vDx&8~Gz7nJCZJ@NW)V7nE-VY3LVHfk zi8A>tEZ~1~?2lkV=;iiw6I#ThfIC_OH*~)wpb^=R$#e|V`4^IKP8Kv~+Hfwq!Q)&u z!7jx^8Dd4KC-p`R2O?(#qfFQ_eTS+4jtzbzVt>XsM{gm&>irRDIy{Ln4837Y5k?EH z{pr-nIdq|5i2jRv>)$Y<@z7P{ByXRxVrCos?9o}q{&U5Vc#C3-hgy#_g0 zxjJy+M7D0>MIdVvQKkYxE4)Jk&aG}pB$$Rl?ti)0FOF-lW}FCr7%a|lFJ!pk%3%G$ zM}-VJUWN;1z&zJPY~?aJ`n$=lh9(+x$6Gz5w~7I|v@(UO0To^|%Kjjr2@?xfLFgIJ zS^Xe4^}c9EdjmPo`QRiYEvZC;t1$x!)pBtGHbw>Q+FV>_mM5k(dzn8$C9$NEY*#}L zqU9TCVZZgTY*4Mq&PGc{Ly<~bJ2FGp-OIb!K}2#JOx=>kl)otMuf90752r@yQeBA~ zuEF@^{aN{myHBneASo|SGb^H-r~7tdsN|xG++s_FW|#0jnGztPCteELxyimYeRz-* zwfXxn(U#wV^_iHLOM9_1DQQnlhMAW7b>AZXRAT_KM07ptkW7M)9~pA|A!+`=xv{Cl ze!BrYumPD*O{W>x1$*pRHU>|`&~Y1n8E%QCd;;DZ8`UPV<%eWaz)9yhn09yMc_1T^ zQp7>7llT6Cd^~3>Sa9cfxomKG5&Z^=W#_FxmnM|=jzopjlKMZw9HvgysV4*%Z7T}% zQ1`PtWD>nbbFO}m2Yi*bLwF$m6(NAEfOFpk{1W{iSXyD=FN}kd6Dc*Dyny52G0Y0> znWj@-$R+w#;hx0Em14>&d@3B2hx*nQ9sNZWX&NAXZV)b%ROn67tE2DJ^p*pSGlb)ku4FIPYSkS z96W}AFv)0L>8V=b`i^+Db03w=c2osu(E|lN(wosPsc=VP^c{+u5y%mX&(;n^LQLH| z@b~XOYZ9+rguBSRR+N3EJ$z(W2gvi1SLq?vywSJ&Oky;i_zJ-~Ra@>nf0tD{d8MWe ziNRB0;iICjOBc3Ft!TMLrKVfN%PPyO6959{H`Rg*`oExd_cok%-8ZebshFqm{;fy zb+a5t=#IsD#H?YQ(nW3G+H$l;?MszZvJx<}9`T!xxShDn=w-p9KU(5IImmc$>sZfC zKmg2WpkD1pztFD->)Oh25j@O^b#crm-;TA{N8&tq;~#2?weNGJr~3-c;@`Tb`{Z7C zi1zD@l^iI1lJkEn@!l*LHQK$ik9%wlsXRji$1=L^m4m)i85rnV^9qE!HO&$^HI(Jq zDQJNbGxQ{sqEdKk>ytsu6NE`$zd|zf4P^iwrlUCm1m}1Ak{}ZZz4FK+#=~7Ps5zHg zp>qvh%0WGS^tvzle6|U40NW9Pwt&C|9S;1C0aIB5SJt~+#UWNawO?oYzyu;Q)ncyo zsWbtg60y6f)UOnA=#B&92lka1w}UJ;uu(5WbUTr_BL*F^pXhBdi2K6#$b59)2#Lv! zZ(IWvBZB+b_so2h0^|r^oZZv{k%+^Xcgbs%i8m|@1trq+?UP1#P%Q3pe*zOq!E<;W zI%3gYK;3eA_@DsyefGj%1Q90EFAQF(a{GBp6F=$WD%>!(*IyXaSCk3KytBdJ7+at76fd%G1lYguhJVrn9R6?>us` zCy*>cyd0ybKQBqD*THthf)yrUc_%)6dEY|w+bTSuJRaFg5Zu|Q64ne>EM)$ zFI75*nPq=NacTvZtlt`>aDil7gB#=?`(eP4(|qR?T;N8y3$o}^0i90jnBhW24DAfr+4 z$yWmYlySlj3f?oa9;cTUUf=5>Roqp*XM=^U>q#Zh#Y?xLCtVqbOO3f2QZf=WJmci+W7X2A(3T{oE!I876IM!3y7(Z`xrg|)j zcCa_m2+-*D<(GTzcxWE=hJUi~jk7n2`;@XOwQG85=2wVUs27C~-p+B=#*lv=D+g`& zxjM(O=IsU!Y+zI2cc!@LcK51hlvYgdH%Tr%Sx@dpO0BF5#0O z6~}PC2V-H%Pkv!7?;Ve*b;|Y9;FJ7X5sLRRm_X#Axl9slr}}b@ofE)hloDCoH@z?R zicV-}>}K4MI#`ol>?Jp#I6r7Gi&=lJTz(F7(Izy)ZvkscbJ^Pq_Y#LmuykkE!>n(5 z-TUz17e&ONQ>coR2uijFQ^kzdoDxGGc3eFF|+r}(hl4ai9=kGD3=a6 z(jB7#Vpmxl+^VacjL@5|i9qcv8k*7XmhD$>eYNQ;cMJQf|n| zw#B9X zi*`Ya`a+2+Qq)?ugcr?YL`1m768{c}>ZWHs@M(-8MofP*d=~r8$YUP#4zjOZHe^`IdYs)$Q82EZxPMy zrGO%eJMH?m`qIxv2F|IIgs9P$;78 zfJBB1F}rjTC@(_*?QfZ?+6ry&oo{jPgQ@F-8k>gzX}5zDlg$nUg;(^UC9dr%BQD zpqEXNIY;f3u;Xc-)p}W1rTu>fg55>IS9t4+^iQ~K#MV@bK`zY(eO>sp@w6D5KrY2C zjPioFqOi3XQ-c3akf3X6!v9OdQF+TWthP&B^?LNf+@!9upH+3z93Q#3iXHZ2NiU_l z(vsaX;%d6MniLdx`wi?Ul&OjH%l2HrDXF~vN2ZLfSFz^<&PmlVA4XC^Gg3G!*dJw{+qx?jfuh$bnbiA$CAEs=Zmgv z6ni`zgC#&L8MC88Mm0|1g8nVWJ%w2z)RPG(ZvrC*U5BE&Z=(#o8jF3WO5=#8-Vj^* zR&By?MgAIGDc+?O1v+ekN1*^vHi5>7E#uP24K|=Dpy>@}9bu_K4d7F)DO3k%rXfi| zTur6cN7Nh#^2Vqkx&Nbo;_ILW{9va-ZxNL1NI#qYc|lU+FKQvz$vk1~j5pf^$C$S7 zkVKBqQ$p7`h0^2}>dh-fjt*CWaw*Y8XV31cR%I6P(D_vNnmW<{5MnDBmr~SQ+X={S zyuC#rRPzXWDwhSQ^Z1}du=0wy^UN%a6mUO(WgsX=nv*I2J86)ME`~4i|6`rc8WS^K zcIt*jEkAa-0}5IyXd<0NJt5=k!0EM?gP8875aWEpyXw)R!_E9Y$9^Z(>k5xXQBHj> z72T+9mz7yxDxXWcXe5E|d-eN8m7Jwn+>*FR#YiuwkgjzIwJNV6EY;ABJV6h-s)S%~ zhQ-!G)>-8sHWt-TkFHjaI+_u`l;btQr??5_$7{g-EKg_+5^+GfHcrn!DV z384G~TS+*?B@bo|R3kzRLeq{8RIj^|q7lAQ>s*@QwR@pS`(o4gP;n11{zkF6ORQ{{u|(|H2hSKWjv9|MOL3{PR_4|L=Vj z|78x9|7Ak5|HR(>ukR!rUF?nB{+ms#HD%HP_5TK`rIu#7NkQkKP^}tZrb)ql6gq&Q z3m3trSlpIVIP?A$Yg6??CkRDHfo0G&03j3V26y6%-asZI#)xC<&3*iM473Y;{eFiJ zqTmokQ-V|aZGvDvxKPA}>`d~+6maK91W+i;R_d*2aoG%eubs&%yo_5II?>o=r6FKa z)S997Ax#xP{fIVG4ss2vlQHGRmOWe`t+KZ(YwZ@bJ5nXRt~@QdPu((Wu2mE(MtEq< zTf&3QX!abCDG^)97#(`vc!7S3YM7I0brc-T zZsE1Ua>{4RWh$PJ&bCA97c6s#KiHnHJJo?KvlVcnZn(CpY4Ac1NK|-3u4`}|WG7e^ z64cUyLW0P;;`t$0h=A)!*6xk|Rbx2fW3h9p(HQ(z@L$)Zr&E?*-oN#v1_=bj`+u^g z)U=hv|HIUgr#9=lsfP4zK&IVRU0Sz5;VxxGO}&Otl{&Y8QbA1Y9;JIKX}Zc93|fLk zu6`*cp2C}pt>6S?0mItkw6|cJEwUM0w8$C=83<1zC4}A z?&bqi?L{Oo1j8m|AZB~o2|hV&IwT=p5jpX3-frUd;&0Wsu=Q=6z-g{(#6Uw;La+^w z&D3MWf?!VK6Qg6%I1mvRX;{c=aybnL4FnU6e!F>>>FCg@$GB7h`jtz;p7uBF-Xt1H zoX%7KeKga2HL3X?mjaa4--y!<5CH4?1N~*)FGsB#ivaJhbbUnXc0x+`Qh2yMc9naY zqBD+msTuw1`IZb$tKzJ-i_Qpiu<$k^@+W-H=DC@EAe6wd{!1=$q#PGL%3O@INxT|5 zb6dU$PPp8GoGIInt?D3eCQj*SOZe{ac4##iI4}y<^%=-?aRoqBuqP8u(uSfG@yBEN zGL%eC-RY*QhR`9OvlQxj;^Eu@7V6pW_{IrnhcEr$DPw#>6~kCkWPq=pAVDND;Nz2f zKnWaFb*n+3d$p10)L%j*Asv6!z{a6=GhkWAQR!&Ic!4Ju-Z4nC9fy6=6N|rCN#8bB z-fNMVJUXWNT*r9vr@nIuECsi>y_HiD*`@XiJnKfrYT1!d$CKp9>+IV6>tk3#D~(y* zQH)Z0oXO34w<_nNVbvN$QKP%uu>BN&#ifS-6fYI&BLzPU)&S{33mxmDPYy(4+}+-A zhp01Fj-OS5cT4ey(Vk+uk1)uNqzOXT?S2GE1@yCk$gslw53rhNRyV8JoAgcL2Xg{{ zWT$+7KU7*T;ZNwg@4cl_66wS6 z8It+>8s(7?funO{bHkGsez~v_;h;lPerR1Jh*9k3kTT}-+rb9fiymkF_*+$6bwzmS^0;5l4wDWw; zY1oD3GGAp@MrqDBwA948uZHm4wB{d(lCU}nRKwwvmdW9PXC4}sRNkJ13%Pgl{~Gmt zSd!IB|C%?HQ2_yQ{~tuXthx7p#Qcahv=`c`cYvTiX|045lrt1S3&J=|p&>q=94_5F z7`$4%K|~CTBvB)@WyQv#hu2mq=DO75s&pnVY1kpP+}R{$ZGa0rtcESAymgVy{?Opu zzaU!o#Q}l$>B~~Hdif?qLu7XGG&nCS@cY)M?~%~|qk=IIUnr-=H!0E&9-Ie8nXH4| zs$I;~nDt<-q#WIT-BIFNi_B;THI65F&uFTVwDr4?9ghWnGdqBqY6GJsWX3}{V0*L| z1+7FZR8o@KZGhgIuEc^3r=>PM%A3tZLegiM=6H(liWvY7ZYnW{LibXx?CIJmFRfKv zf`=Z$LW*~T!$ME##bzY|W`B;huVlw%<*uE8&20lVmENc|2F+7$t`&Tr{;YPuf%v9z zp%p=s&1T0_N1^VH+k=ROh)qvCgyV3J3SrwJKLj(7z)h9~0JT^vs#Q)|M>bvC!ek{q z%v+qq(SY`t&5?E-ZYY)zs`b9!HzFFJ4PEX~7}|8xOJon>#efUjR82njZ5 z9g+6;#U#!`d~`#wAE`v%TKczxmYi)$I*_F8y5t5zD%M7UjKR*Zo2~4+722v*QKSLz z*&^!@kZ-_u9b~Qums0v}D4bK%15aEshtIQJC#GtOI&Yo^E;}o4EFX zFUww@WWM=eW^89xc!j+9r~PlYZH6_ajB$?GLV;gLo@Z9(e_ib_nu-zJOXU>}Qsvi+ z>4$3cVhCd=^FV(5)TF6V?lZS>i#!B2*R26HMQI_;qE16_6#7L;+;(A~3HzDU>Snwf z=;fZU8f7LMyeN%{*rCL!L>f)LTO}o^ywD`S_-rIced5n5_r7V5@+K)yz(nZ{{;(Z# zR{*VHH-p&Ic-$_}hi}uV0zzh_((?>>h174Rl#XNMRAoPR1|X+`L>axnb6m`lKpGVr zd?MDru|Un}(6=30s=r$m0RgY;%#!MT-D zN^)z*0yEc0&Ca9L42$w4>~|FA;+aTLWu4i0OmpB=V)<4i6et6izdfrY0(%@pg6ngO z*sp)XHTXZr@q^~bI)fx$cwz3)Yu$QWV^_R~4q>oYI6_(kxBK6y5z7}K$29KXwIVj% z_{3MD)(eTYTrl(1?ep{GD~Z>-@7Hh*@Ru5R-~FTZ49%W<$*A`)TjQypMz!H7^@;g- zm9xxXRqli4mg*6TO$*VXxQ*&UsnIbl5a6`Eaj~SUvm%iEjMS-7xQAlfS24L+q8!~ zV&eI-6~nvCQ)Ju*CIIlk(F5Fz&m;MO!%#H=54){m~J#<15{N z3^`%LpiZ6W)tWnxUK>I-9Yq}RQgAv1jwLhZ6%k;MUQs`6C|-rjLTH}Hd)U+VQ#QUM z=MjX3nAjuA*W|tn*?Swwy{uDFRhheNpr3>rJ~R@bVo6r07V~kwxk;5dAzqe+NvwZR zN~Whc2+CDQc2c+-{Gla!93-uK4NHK{RC_yUYt}aQnf06aM%P=ah?tGkPWXmKq|wd`11NDzw8m zV~eX(tvHoqZPF@N`f<+$|x}_dy@0DZVJLjfVX`PtE@lWcKiBd%BaLK-Ha!Y0x;PR?1Q; zgU30~M-r~WG|Z{m*m1;8uz8%)Z#!?)Xp>#+)I;XBlOh0{Qh%gLOkN=ZHCAccm3zR? zUV^R-d;+l3nK^a%!Ag8T^`s+wTAWM3ee7e2cyfPBL>zd)t=@|f558uI9pn-f@3w@u zi`@uF!I_3$U#8H(V5L3IC0|Re_ID***ITZ5+sUWhUEdYjV56Soy3>QE9T-hw=HN10 zn)z#8<?YA?wungOx>29yjX>yV5ns-iMnN=1)zzBgm2Y?!ITv>x zdMHiC?XcC>prDwkmAOtbb+`ps9oJTK-(7F6?$7E;N#@YZ`N7~Cs0_Kd?|R`4{SB;E z_IKK%3 zkgDIg*2SWyN<}Z2vR;2r1Uni_NvpvoL40_#yqzYI+O(Z9;~`1CLKpL+eIzo>oA%u% z&7FR6Q}ANm(s`5IZ<^A4RR9TI8cS$%w-U~;LJIwKDev$Wx<7?x#h3bigwCOI;nxds zL<>h40}*(U7zs4@vg=ofuP(g2tHpj&OGgP)eU+!fF~<2bapFciLu{R@4FVNT%Vv;> zEtF5dqS2^-Q*7ok>Ox;C8Y~bCI;D5>w1@&UN@sxCEy#e;?^7$~al|W$KS+9zQo<3s`PklFf zD$)#lXtM(r&xb#;EI0BKbdd=bRv9Uw~)4$$#Ukk$YE2z}ptmJ!|s*CQ; z-GELTV-aW`<%_!dtU-JkdygJ|6W#cBe0kDah3_eS1T zNcO}YO2@ngK`;~tERyFvGF2AvuKI~aSxN?KGwUV*2#OHm5Rsx&22G@ns3waEgU-ckJQjg zRo~rW2UWs;o>$<(`v8~UkLqIHiSJ^uCUpC zru-2kfkdKSAY4PTcE;2P+M`!Kg*|Y-;Kj6i5`i$*SGrrp`7KN>41;0t?y&mKe6A}B zcXR{Hj`wXc5^u0CFh0FW%glK$>LigJ{BkpOrw8g*-I&aJ@TJ2-G}&le5mo3wjRV<( zWR-UQJ`(9wbH zwLyq_{b4G$sWSVsq(5DN7dVr5y;m+-(Rw4o-S~Tu`vQ=1&ZoD>Rhm*EKogZ}C9_Cx ziv@S4i+{L01qZ%vNk>xGf?iTCs292kJszfvXanbt|(n*X+w@|mr^ z(TY>+#F9!s0$!aw*ij!-p-Lo_er?0!f3hdPSKrZ17;IPbrs%EE-|fyLuUQ&ti4q%b z87#5L1}jEX>A1k8ROeSUe>dNK+Fs3a-i@kQB{M+hi-%m^c7%3ybGJ2KL^B|D_(kW{ z#~CfCmX))rqo)4^v%;Z)$#P72Z|N-;L^GmzUCqFg*k_VglVgx6!i?Rp{-ysZ?p<9T zapU#=)VV_-538| z$-P-A4KiJMy9#16cHWQES-~Aai-)ECFGwIUZGRy2O0_-ZeQ|Wr?beM<3=TF{>>VCA z`ou0cbJ=Oj+*tNet=VvLwKHC_GTUXggj*~2aELf6^p(DqOCK5f;pU~XJ=K` z{k@Yf&-Smq@@mA(Ed(K9Av^VHClArt>S=L zXSYeXO~1918L7TZct=JbW3BunhYW5z_YIBTPm})e{IDzJJg(OD66n=4xXZD9ioNO< zn^d!P`<402ay8ITjqyj!WN?pj=veDIH7{YCEwO?&$HAcU0#6!oH_ zLMmfootTHlS<%Ki{_KZA4yOuB-*xckQutfkU)cM->%#J?LXkJ6U*9 z_q}E3!FVu*S+12)X~SpgjN&)iZSPAqr6JBz26zXBKdiuxj{w8{D|W5M+DPP`h8Zy^ zJO~$QJhq-L*He*e5_grDL1$xBDUxnRGWsi@z)vSg8^x-}RuJ|}cB*VSxf%<@+XCAh z5e6o;MnVQ|?cIV_99u3au5u1MI#J4O29+iLH&d9L0Ou@Q5OF4n5gpB%D~R`7;YPH6 zNbL086G-QjwS}>?lfhr(^3Iatydt!QqbN3Uaz`Ljk=t!n8G~Sw-K@Ck4un-yo~(-=h6k47RDHyBigSYeD9G1G-&CC{TUcTXA&4xavNNcIS*3ezipZps^AMfDW91_7 zR-7(cx7tW;N4#+|nhM$FUBA$(MMRLiG(hIq;G!bdAy}xcPjqC`OsqpgfmIpSpy=S2 z7dkUmQAfPJwIhEZ|B$vhty7i_YuIX~SFI6^Ps+E5QtRPg5DZ771_d*&H24vYt*Qfg zFIIBx)d}_B<_Yq3mv_CW9-18*n36$x?T^n6+!rdZoS61QgNR2=wBVzLnQMIfRlDt# z-Q`X6OCmyhEh9QeseImxfjfqIlJ1-$-y-em-0$0w545d{gRfU1>r}Rz4!)5FuKY&v z!P`~UZcR?O?PSP&2XI8DTr~}9Da6f?8|2<03Z3rE{~I`1a6dWHMifC0F=}j@&r7Bo8T#`U6Kt+ zt!jcstTs2NT8rK7H8%0t=Dhna2slAP2qTl83*31OSWIVnE%_TdwnHK&K6js3BflfX zml`CVyoV^ihDsvlD|5|8evy>aJ^r7P)Ui!AU0LUYB?lBQw(;;8&;@hfK^SJ^`|$H?i=FiNE;8w{-OV zkG7#QlZ@t_$^vzJ*xw_Cg;w;cTu!r4t%7IqzXYtLRN`Y#(2~?!Ouom#lhfk0Bw~){ zl`NBJbCAVdK;L>>f~XVM!QY}Qjwtzv4-IQmidFG!2IVZ-Ww4f5Km{g2cKiib za`5;7co!8r{zVjuC~>|Na%XWE;bv+>uXJc=M~+0kl)f&p>w*E3>@6sdMacn2!s)58 z<`l)}uz4@xM(ze-s%b;jU>8Rl9ix(LZ{Sq)ud%w)j>;5tSN7n3!qTb|Fp99;=h!XY zuP7dsKi^Vj)}c9)$2}^Fc+>w@$7T$HTyo>tc|tsCWplQ+><51rbQe+B!sInRZZrYZ zvB5~Q`MJ9M(!1Qx_oXR1Jx$@=bl^W{VoXU$Dcm=}vo~r@%n2@(DqXzqylYJ)OAb0p zY{ya|=cHDnD~?#H&U&tjoI>6$%jd6g?@~)*C`(I-IIEWBGu!zwmsuN8t_T#-2>LCB zQ%J!{f?Gnt)LYZf_&nia9V_l2hd*7$h!N?oJmBV15M7++_s{XQuF`fYD=s5jV$ zL2pHruyBopg*n5KBMY29%}e*+Qb}q~uyLb}8`s(4NO~v&J(c-fQ8<1D&ZZ5f;X(A? zrwnY5ideUgXz_a!o;}$^fv6eBC(HD8@hNo#uB%0E0Vz#Lja{jjI*o|5p5c;khx5T@ zYxt(e(3X>=p3gO5_#eYB#ToR=K4IlWyDB7kN7U|Wp-wC4$|Mjt`XWQ6Z4s`;11G+h z_4CsHlQ{cjl?<9w16wqCQjq`iJJkxqeaB-%HL+0GO8n=1mn6tt%@hu?ucFK8(6!|` zxk?@)J)){GH|(T`!2O7YQV%I-PI82ogxk*6nd3YcG|g79^V}5O`?aNbK&O8CIx1SN zFIpL?11?p)UuP_KaB7eB7Y>8?=we|C4X?2D)y-4Nf{)ghnsm+$wccR7==!MSvE4$3doG6lKUl+d256!)ZC4lA(_ox+EA)=U(j1y528I9_5t$)cdT zB=O{%N9J`aDNcIppV$HeEXN{ofLcx?G4hWb3TKP`$iYc?K%whF+r0bU+DkS2>A{1~ zJrZn|Z>Gr&k}7!;#Fpq%RDw1&vTxbnu>_OXf1(oI7@|02j489DESxXLtChnqCFiHf zo?N1p#Z^i3dm=r+nCW?-6tW`w;^V*!20w?~9;t&H937GgWtTn^L^>vHE5)z?2YT*i zYxeUm;a}EGnB}{H4YjtOfFjkGSvcat9;z$>4c)gbTAkva+rdK%XdG>`o&Dmd^i}4ASvir%2cytWVorx{ z>%s{oSXA;mZ4A2dFAaKrIebh16wQn+h}4$Uh&&iWY?JFr7h!yn!sgle%Y3VViDGqC zVdJ1pcC>>ii{0|S#19%wEJ$pFO zMuRJxkuSt*+9uM2?wou(5h3cvtPA?sSA_*#DJ@!W_wi10bcmnBHdD&1c)m4U;2BHS zA8l&*5H|g64LRL*2kqpnG(${ya%nIG`&@&SIb2H1X<3FmYS>Z*vSAl$u3a#optU5n z%|Kc9)QJhXHz9K~WtCMNn_RMt+h6emT$??+F6+XJ`thbHcD)l?CSr$X^WBEQDvOF8 zHtuqNlaJIY8FV4l(&R{?34vXA%2&1TXgV}`+c>=N9yVqgy{E()Z?mk*8h;*O!!-FA zL_GcGm9bsaU=Cj0VMNLrPL@ML_O16(ub`_}$mc}f%t#?Y<8)FFF0*&{z3_}dK~I1r z15d#ChFxm=g>AX{g-wY2g}pNM3oFJTXjeiNrg)$ueILi->{U=&)|bfDFKrYq;wB+J z;b|NnOb653&ZOUSgtIPmr&_dxUT~OOa4VeN8Wa9*J80X_fljGFun^pZfROsX2Fu@D zu$HLLD`JSC2sYQ=B4BC^Vp+ySF{(qz=!8ayvf;21jeKQ>PMo}GV3D#nJJ{80epb%P z+Kqf5(KWw>Qv`i`pfdWYki3?&hVx{B+Tw?ICl}8k;wmQmA*1iAQjPVpeBApmz2Hzk z=WMXM8|v5_=}s1lpoJ6tTrEctoH2ecKG>QCS2Jf5QbqqX^F%iP6|6Xlv=VPi{yEtU zy;V`wWHqkc8w~YHlPw{k>TZoLOoaMlHH7(0=E?oa(m9hCTJeM8-UslQ5BrmPEyo|E zUWob+6->L8*JhZMCgaqRn5pz209zfiF6#NS5uZ-Zln8cEN!mXr#Uz@5om)GWJGt*! zf5y=LWWJMdfigfWGhZ=N)WTE)F5M`H^WkE+3*{&}Txq810`idHQ&<+^sy9x+RH4BIX!ls@b|uHLqUA$>He zwu5}Ly8xX2+p0S9Uq^tEi3za#?9Ug~k@)$qN+Cl)CR@K3PcNMb<3M_Jt&l)NYpw76Yk!3>ee|Mf3KL z?rJfOsxWDG^P2^lP&HDFJ4;qt8<0{NM~7rY$gw7tpWYsM-C*V^oY z#SKH7yPZHKuB^KW8wKpYX>zyCZeK=QaRjROQz~HYQVP*1#|pKg3wY)%}@@TsOyJ@WDY-B`K8tvBGZq@%U3*cMYp0aBdT}U=hYL>`@M&#XTx_ic1K5nh3H?NIU0)^Ekq>ahpAVGvNSvHD){fY zEWda5l%|n3fvUl)s4iIT@ar=X&pPOSwsi{a86)`0!fxcr3)VFq_2S@DDZ(%78?#V! zSBd4ji`ittpH|oCgfiEFS7v@W*p<`$;$+;^iVrl3E7u&Ii` z+m0T-_bF0-u`u{7`H)tnNqFeR`uMQ>pn8o}UARUH+Wg{Ee((aV8wMq@s<)Yo)}BY4zfL#p3l+ONkVp1w{4>oxLoX~%JkCl zxs7#>zPw!ilIrAWqv-lR1`b=~I3c2^N+nV-W@g8OFRfE;H(xIW=Ixp4t_$Xf{RdY0 zhZ|}6i6{L^+CnEBA=qbd4ZT65W%uE=D z#@!VC`aWd!w$;CXa_AF6yyXTIO@qz%zNAWN>x?=!cgYxFs0@_$W|sCUYvG(-T%-t{ zZA%K9hXX4HV7>Y`DpOtPZ97ewEHL!f2Kc-J(yOTXvW|IQj57_4m%(fsZn+&3Ou$3F zDFa`g6S%=cz4`L)bdUD{tH3Rl#Y8!!TYGSq!468K-SU_EI2Zgz z`QcA`Xtg>8OaalfpXgj(cB~*+yj9C1SkE0u;Es7I(|hr$yv4-g zge7Jv*)0NPAtfRdUbHB-+NFCi_OLFMje>oDf^ssA&*VX6L}M~f-sN3Qr!)xzRyTBm ziinWl>B0!<*7Ahsq_pjCi>uc?G>+R@TeShb#Am_+;hosSfi`&C>^6><6xdg@b{ zNm2wa@32ztW75t$hu)U5=#_|bw=m7g&gxtD}AtNk%b4o zJ|!yJ1%tI8wWwRpRn*9Tb>1+L@yx4ss0cb7%gMqn{C&v$bdOkKt>7`~*p_3v=5&eZ z*rDj&%R~eR$-z$yq;JS$Rs*_MF;>9?;yUv3JzlS!+;msWoy;N%oAm>TP3 zBrdr;xzUy!fh!Ofa^fx!?e>H_ETZ$G(a1^Y>PQ~}4%Y8}hB3h%ZvSUK?NLJcE89XC zf!62Bcu0@Vm9Zv}joQ61f*tyAZLPq*5z?C)7(j1AZ;F4g;Y=kQ=xar6=I^UZOxMxY zE~Ne}`@>15;@BDWDLdGCCF4!t*~$Y4Vk|)#gV1OcYsPe>k1}psYBbqOCIpKiYz>Ga z4TKtC=#6o6d#J}))>}?>A)i(re{{zIbC>3LI0zEH3e3kQ&bg4*KE6}TD@2SLDb?cE zOlu-`3wD4O4eb+MWMp}?l&EN{0W2(i0UFxNQ3?tMRT|RFC#`;dP!!w+C&p+KH3z8@ zm|)B#IP2xLC^27n1@*I#+Y0HW?wj^`dUb_Q*hRfFX&^t88U|?#a=&nB)C+^$cTC@|5h+*Rts#CZ zaM|M(77F;@dejzn{}w@TPzzY#9V5^(Zx$juN|g7XgT0NM!eYCIVBSe zA^(V&^1^Oa<{Zl)!){IEoN2m_`n2IdkO^98-0e{S)p!*~?)!1QM-h1M+^w>xE%-k_ zEzQI(@b!}B`W*D>^|N>6AJOOJgmdn1QO-#*kKa1S6}%^$r}hy}P_gVtkr~Nv&1^!A zH26gH#NlZV(bmv;yl2{QV#X!t`xWPB(6PuCwbiFo0u)2UTp zy5Zx0fdn1lLh>qp3Df2F8F5^`kRolEmGCZHLA*kMy{uvwHw;xA5TlkLbX6Q{zswGE z5en;h@hRo`oG@JtMhn6lBmAcy-h@O|6-rJYnETB!5vdIL(tzlQgYPF0b7!@|i=Kpu zpF9g0ZUqNR!|cx4Il{!OCEULqbK->Jz?g+>hUTeu3bPUV@!5`E5ImSVPtA*Fucspg z54Rxq{&l+-gL72%cvzdPdt`a#bRMHRa*l?RKO6FO$? zpY|BFkvD#EsCe>e#nwxia##={7SU$#2+Mq3@YP-y>L6dZajDg9$EZT3ET5g^rxYE* zzD2&{cakNbk(RoZK`-YX<_}LaU{21eP7@UL#Z`C-I8cWut-gEts!Hl`=@~-l1VVuw z@-f_OL>>MT4({MYBWtX}A)a~aG^@W?;Os{ahD`4Ah^tpR=L&jab`H5-_}pXsGjiq* z{CNW&m{5kAaQ5KskQ7^}`XICk`Fy+vVp8E6?fvbdad==ls>`#BPw#TK*~yYv#B;r+x+=Sah&IYdl{ZEOc0K;am2Fvp7s-}zJ@@OqFp2Lv)l zS@msdr+l@_iiB_n<0~7 z!oX5j;DIW$8Bsg^0yIH8^K4MyL9#)ZaPah8JM{toGR@UNZ<{xjLy|>b3~s)rnQ@D% z_du+UcIjZZ6$puoo_)Q35qQTl1=)*^*`~GkwiPkuX`P_1|DqQL^%li{)xe_tUN-$oRkVD5RJ7IZhc*QxI7RLsX|kG;yDG^>MW zWf`0L_2e}-GDQ=gK_M_}#7S4Q7gzNqh@#ar|3oD8F#E+Z$BPUvzK6(>G?V@0zU?-xk$Z}g8m(sGmTr#(PQ_c`}U1N>{ z;=;Hi4s&cFQ@j4{75LjL-UNPJjOEoiB@@`%ZFOZaR-3`|nuT|5K_P~5SOzE(_Y1IQ zdcduy4HWLtPX{01qrP}(L3W4xH4{>PA6fG#MiO0*CB~`)n)MUDC-n731#ew^ETS&; z+Z7qX`yeQVtVf?Ff(5vWVT_>}v)&3F8tCg~b2Rv9j?qB^$M?wHfdo$Hg@J*%J{~wX zu1Ecce++2VDTEP5;>-Tp4V%ro8W{X+f8!iUq5wKupeR~Oi=2pIusCnR?g z@hZWl5k)w9%P+XxULN2B0V%U=-}hKOGb zmSb+S`bHH7wbE}PyBsIni7M1=Y)+l`TVNWuf{|n(-(rWIj_0OS*o7Bwyle}|00r

7CGa%S-^Cm*F$G2{7mTv_D!X6F97}ncrFW&`nW4o|E3gEn zOBT+x-(fT}p9urk_EpUYAU7od8v!J+kf(i^2}<(cv(@uaMRWoWb_ujc2g8cJJ|8L% zBPk|QKn)n_VeGoEu_xVF+}x5=V`T0Wj{l+JUPlgJzrX(gwNyalXp6kG##TNCAMW0O zL|qqOTt>*so3-~3w6pP)Nvh?09DyD5S^OlIYb-Oo^Ee`Uw)&pBe&iIDOMK%$0ORhd zg@>S%lbUH!E*4fT8&;j9oMM~ff;j1Symr5gi|zHi^^v^cYQJr98*m!1IO=}OR@>91 z9;HrFuC19MT~XNi(Is9I4j;n%3fA$gC@{DTUL;-hBhUwZXF+TD&IJAwmqG7B1Atv2 zG@SA|w|7br!TzT&8@Y!Ww_%;odbl7j!k@PKf@4VY?8=$<6;^J#dK4wEyp{~8i%&zNPZG=oppWl#&I35At z&9HCK-rsjmrbDQGP<&-A=hL#RUe1aY?CvxQldP_Z77qo|gcUAU&-++fDn(+yHS>yT z19-6x5kesrd8`Q5mo{1h!wu(CQ+G-p);zSShGIN^$asO5R7;d!qW1YAf9*6k@dHxi za&~ST%lx8;YDD_HSkS@h*5a#*g!)@%$#{f>5jM`WmEj^LF*vlStC0~zklLH0El6p- z4W;a!i)LcxPQ8UY8@N>TnfU^|o4xBKGkTBtO2ynhLh#FiO_U4O=W*xIkdCPY+%^P9 z8VVvBpM1$L<=5qju%?f9=@3;HYmMmOiO%hmCWf8KIu9U!T})+|Ut36zu??4?p^r+m z(2L#U@8?+nEp;e__6#Th!3%PA;JcZ6>0WX7dCz64!?;Dw;% zI#af1AJ01OJiIb+!L<6g+)*ddAq8Ftc}AuFtmF>r!0ubfz6z?rqrNc0t9!4ARZz=D z{pWeQXXRq)x@9pftHk0O#d=bs9~~h~$?H=r6x2Da*os=Wr$j{Js??8S!|5XQsgk46 z)HDpK3Of<)b9R!($LzD*MM^5 z&z0$W7hUak=DWIzjGA`UYCi}c;2jXCbAfKe(Xt-HWNn_{zIj?J0hd_UbV;)kjT(V} z`&QKeOuid_d^%AZwM0+4Y@xz@Lq}#uciYoRf_a6nTv7S z^|7-*AI3ASO;%y(DDAl2?csF?cfl2@cTX!iI<07b7?(F9^O>g#llokBTCBb9w9CIH z`q{$kASGhc!KcHU*`{(Rd8p&iJ=8^h@Lr#1#I)F(sRXS)1pOBGy$gZC2?LnXBEy9Q^AS;?kcfj)*bm9&d%6#NqbvbV=+@3%lG08`& zMgBs{5J&bP%)8u@d9d;`ZA-F67nm;wWsLc2 z@(xAY#&~qVdI`Lpa_L<~d@VlHFlU-Hq<7J#V$)qc_$6VS*Q`$)0_?U<D;MbsL@kJg(yj20N!BhYU6aJJxDU_?zxX_P zdtS@jkAFG&w&3DoB8?*u0xiX>aLxSGRWt&#vX-!FcEY3+4f*QMmx2{+zAN}v`9*a~ zI%heKEj18+9yCAY2X=GxvpFa-A=}o0Pl*%EKco2XScSP<}n59=y%QG zY^&F__`FIY+O=hqpF-Oi$0jq|?VOef9DVfv{P~llAzhs-xG8k0O5X-*>Mp-u9u8tHR(8UrYL0;*<1s7yD2exws>F&cY9y_} zg}F(cSs3O?{AhwW4s%^^5iKpsb$3JwsirLyt}jA9?4#)s&rS8S&0NW{Q0mHeQK|xtwS`ZMnZq z%{Z990*m~JXO{SM?Y65nypV#3j05*r?5W(V^JC7Lug;Kar(GdIX? zyfc_^bgD5dE+14e4NHYgrdH+%BI{xy1$S&I6D1Zly~OZ*+DT+sr~5BN`i}Rmg3^z> zy15@Im=$v2Am{|BkqxVF@T!73`DUwp+bl?4!3oD)DU1@YdPbLsjZ0JGNgjG3oAb-Z8$><1?oEQ*ob7iyP7>Q*8x31ZUTRt>6iQq7 zh!^^r1@GovC8Sth3Mr?6uXsE^MV|Xlh_4M~hiZ0%baP+&wL2%{!+4CS9V>Y)T9Q+< zJ5m8}&o_>H(x+H-O{fi6oi*}!xA0CX)25U;c7eUuDPa`C@vFoU21p)mYI4FBU+0@_wX&qzdTwWM=|YRAEZu<9KKkc3h5`c@a-)c&fO*`oXVf8v4DY^ zEM4m@b4mAhO`m>@huysHoZ7zooW=uBU(so0yiPVLv~V#bXj(8RXOgIFlM|!2sXaE& z!QFY-q4=PoDJ{NY1H&qyyhWEkf_Jh$#IaOa(Z<6GB>SBE0?Nh++1X=}IV0E}!8&_V z)D3s9Nt#R|nwNbHh3_Op%L8$MNhrX5aP{gr&+-tipXG;SL6|2JZ(j_q7L$HVQ+9GP zwe{EH_Ycy$8(`mDZVyLH&^~k`93ntD)1zG;A5s;OsZF$al{Ef19_+D5oslWs65I&} zllXYqsJ3$Rowypa-#ygu^u2g{!cc1Ol}9d@c)+WEu_6r75;c7Sx^vXnRk7X&JNZ73%ILW?AP@U6NMQUDq|;1!Q8Pbrs*O&Z4ai z&d-Bgq@L)v44Sda*04N@YgYs75QW?JPJz?R%iK127sPBAq$J#)ZmftDwu@EYefAnl zK(_H9!Dnt@aK*+gKaQ(ys7S5|slKUYF@6ACSyz*I|9FO$SnjB9F+b~3?wS!%fO%7C z_rteSBuvg}Y|57SoI&XJMNu!|%j+jQ)!Edop*hLbYFr%YLkl=xGtCXLjASuRw10GD ziQPVRPo4=Re39_!Ro4+ij>{Z8xBd!Gn4lAJWJ(1c-#n>X={^*u$xF)LQOyTGx z9s^?h)AzZXEunZ`FESt31}Mze2Y%sd4c`rOT^=i6BcO}qhr8u9_`wfY9aOXwP_*zF zX~UXFR}>mUY8K2$XgW`vg|xk%_QGcLwsTkM(Fo&`-%JZ4Dxcgr&Vmj${LAM)9aa6t zn4dC?njHs5>1zp^9p$PMdEuy`bZApph$r?A9PTiakej8o30?##uC;{p>xiA625+#t znJxiKAv@))>OJqCvZj$ED58?DQk;0ALh)dlR6o@zplp4_r^X65X{BnUtlMr8z=jK7 zFK0r9oSzU6V&5xINl$D)KnM34xp6X6bmoS!2>A6x*PP_kz2=i(g{V$csKA+&R(i)jHqQ*~$GHQu&lb)Zewy%9*&GKUl+NkZPSFCOe3oD@}p5s@PGj9OT{Ln z>}qJ^(vJd)Gr9vaJzRxDg(C_m-J_-MLLX9^Xk6hEESg(a zQ7lcF%cMakwv|JGkx0(1#-)iBDaVY9F9{k)%9pl3qdaGSCMTIE^e1J$}=! zcA7qZs!&rZ>2|E!`ljt@Wo+iH5&C*7K-yz3=P9G5;N z=W@jzWxS}L1D+4vpYdHfs#^k1FJhEegj3<#L)5EXl*FxzXXC{+U3TbiSBnXM9^tyu8y`-mJ$zH!R&F~)gYv^pQHl1^l$I^-G zHVho88VUW%CHRCCsX5666sa0p{hB3_zS&i9`aakHFioO?>ow$Fzg1a-)UOw~mrrxwt4?*XYsQBqIHaxjBZMh%Ce%kk*&@B542AmVp>heSa8tIWEm$-x zo5rLa`;z}vp9wzST0lHO~0r+IGC@A4KyL7~5(n$M__Y@u)c+33UJ z^(=;Ff%~cs(QtPy&sig~z@xA&$%3BTQv3Y@@0kSKNYyp_!p6VocJK1VIo!!|C!f!V z;H<#Ktmd>mrc4;H2y8duV#`~9%Sv8bm1rZW$*1Z@XOIo|ASjL^v{Eo4A#gwP=qc@j zj4LmOSphYHs{}+&!OTJ!9@ogT0~7OgI4KL)E)vEOyzGJJas4wWwK4ah*kCtZi&Rct zH(%};pS|z!B*=lDARO&Ed#`d%v9xCnsn-tGmFKn(26i?$T1xWycq+-v(DLN=(G%v< zZ0fTsniJu!6T_^$cfjx8?WT^13YqfArJEh@qX*|UY_WgjeT4Mp3)Ic2MMc}5z+_+{ zT{^JpRN%i~1EeXTswShP_~#U3s@fN&@p}Z1fg>rmS2mO(AtmlD-YZy3UZ%2Pz+r)> zz=~(MQkU&owHTT^bnTP&v*5VgB?etCl=NemtWKGxd$_)TZ>X~iY299*@v(GP+gI-?#0O&lS}O}_Di z6P0-@?Gj@K)zsd`Z?E{Oh@b!|1^WO`D)$aHWYe(2ojVBqrX_lhYiwB25D(kns-A%U<#}}t-U6F0*N%m=M>FYXJ0~#@oe9HGkU>NnCOj-SBmqf5R9FMJlQ^+kz1M<;k**Aq#@SBhTDf?&%0eqnG@y_3_b%P%Aw zu9Pf?jGT(dVelSybCi*1fGF3rlPgV+QlfU(d{%dMVT83^4U+5LMnwI5M&h>N?Tts= zgX)3%R=Ourb{NVMhtOJW;0uZ)aNmJd%95j@A8_i_a|Uc8sJ(C;6gCd{!; zp+O9mgHcuphz;b#oC?c-XXKz@A(i6xEOY!yAk(36HLqs$3o7>r>!~+NNAYVc>nP6E zXGToxBjjYR7Pwr_0Z~pYZO<;d)ALy0mzO`4s8{SsxbhbzQh5XcvU;?RvDWitrgS+t zD1x$2N6~tfBTxh`sBam48@v2bb$GtYvzkntW4B}>S;0}sbQP^RY5e#l=`J;fKv9p( z*WKo0eGAXhR@-1-pqZMW(V(rbqFJEHQ&Ma_f1n#h@;D%ndQ14~ZLW%iI33M%69&)V z+aZ-g9U;XZ)xyK%gp3irF+m0o_q zy=P`V?5=1w+_Jg8yY!?r5Q1)|k0hYZ+b@`x0{2sPWk*{g44E_Cr9f!tWm{&kQt-wGcHwbPhWMO1;k?4$)5*tDZUF|EvW!Y2@!MBV02 zy}D0(?9z@ag?L+wKpJs2@ks7`Jm%#_V;Wq}lLZVF!()UQgq#8AXz^NcoMsKucicNI z%s!8w1!ksknvA{O#X}qx!?TDR+8G@sk{~kj-K82uaX5PVto2pE6E@)op40Cyl@_F{ zTgeV>{p9?d)bvQ@6%Uy*S-UvfCtzAf%bs_W#o5EVM^}irpo4a!7sW8`dZ!MEkY;({ zD98)CI;T`9ge7-aOEgS{aKSI!+X5id!Xkva*e&QrTR1~v$`!bz(moB8#TVEuGhusc|{6gwB0snLYRyXtg%d=|0@=i-T+v^pW*V`Eax6*bt zrXa^ZeY8Y}`!>9wu*+ZP#FDij9e8Mlf@pUCmYwC$D7F5(e) zLXTRsS*+eURqke=zx^^MS9p-hd^m;u!;ou>AbabV%Tp+l&>@=JFmx~lFy!s8T`fFW zz~kkz=b_DF1_wA~*6nC-_{3OZhh4Ma7vWyeJ<4EPz<9Z@`NpZIYRO?>Qh&dp{Uz9F zZ~k2I;Zc}po{ihOP|XmTD^AQgUjVpN%&%C0H z$s=7i==+%C;H=jb84pyt(Bdq=bmgg-<93mCsBSl9Jmi$##6_FOJf{bxVVBOZp9O$s z)0V8otvh=z^6~RT9uK@^Fpj6}(xsp^L4g6a=JMGNA~n7jX6Da}!62`;#4bo)j$RfebanDW%=kt-mZ9!R%(ARFE-Ck!8F46BF zn)osrcQ6`*_mYio;dN-|9C8f_jpAfFsPf+(XVajWv%zeh)5H9%OQzlx83&`~a2DG7 z0cn;3qtXe+h-eWkW~@!Z6$&0hqbd(;jL|PM8rx~QdumX^^sdk9b_=yN%z2tA(UD*u zcbLbzlHbnl35(jwodi<)0XMmOXdGwgS#}YurG%vJe(&_mNOB(g9udAHA2b(A$3p#p zY(UCZgp(CSA*d&o|2$&cl^7W&-Kb;fKc=s+YL*i5GDid9t}S66F0+z^5Swl93F2n`^JIRmEZPf6U!z=MY2BOp^;E2UV? zPm!4o}+?k!b*%Lwi7NsBgI|4zY)be!ZqDp9e-KEbBmrmf$h}RfzCz)#&#jQ4FMZAYHfzrd@%5+t zLX{S&`aL4oh@DISEbR2Psf~SBKB1|Cwa0}*Dy1r3C|XU7%5&W^1dmu08rEXP~aH+{w$HB43o@R@VqL;o*6^T%rXzn8p+D=R3q-$QwP z9}Y%c2nyr%RvQdwg)qcgSEsn>M~D@J{*X{`yiMq6`djY=w*%Z}uD zEVB^6zm?d*oUVAZ%yvXZBq1uJ)XV=i9MM%Fh%|Kqx3I(Tvr4gl%1OBKs0b{ma#e{w zGaYy4)oTrCPBHA;L!T2yy2;iNhV~7k+A_F}%9$s_@MOhE&xZI?90fn+$=TXyEHhKL zU~PO>m^*9^!ch$PGM70LNnB8abQ;St-j&{;c!n6U)rytM;UB__m>Dul+gC#|t_fZv z3~_$sKarTJ=W5utFpgNczjAlpw&P94kth^AVhAGME#2}_NhCsy3MDjX3$z_Hx!W^c zM3E#WgJ`D3C6KsI$upWSX0L$754Ub(-1&3)10}Hh0Win6t`zqAhd&w$#Gc6Yje>-_ z2&0UmB#;DdK34hU_C7xF2e`loSUmBqWqZ>C_un2<5K)wolu%P=Qjq*l1j6qK1G(g{ zc1_p?xVN;sLGpT`#`RwS{q`RX_+rIh?fQX4;2wCu$=M#{2)45Z**e{L_GMY=C1{;z2+# z+#q~{@>3!HCL!r;Yx0LQKi99vEdL%0Bm_hZ;NXDazO`&`C%}ilQ;#ah$PAl1KgVouG49g`tXYHgJ!T~^=KLqjYu*1G z^R?k@=_rticmQ0w8+@wW`)AB+KGi`^;Gg4OLw(I#*A>&B?bvqidQFhTN5!m zN06AEt(m2{77#z;#y8^rTD8S&Ii9$CmtG$lP$< zpYZYTGGEW+{n@T%meBZ3ng3(a;9rRm$SOaIjZ|F(;s)jmFMwnV`br46W_xG;A1U3C z{O8>KyYN5^|JAOAzkmPN!h?Yv@el-dbpOG!N`=mJ_&`Ef1hT=+be#HIcne)&BoL=7yj@;p5+B=KiMWYp<(y{!M1Yjhrlu z?Tj2vziRgfR}58PFboDHMg}Sj!1unjY;Po>3jI}XzTqoB61gGo5C8q|(h7cK<62r2 z&);N3;-QhHjV8#^(#+E2kGZN}SlP80zc>Vt*$a@_{2Qr13cUX-sT-32f}bDdCkJ{m z+WJPTUVy0)R0s%?8%%ln{2;#wQ0%#Yu8Znlbmi9oU%OmjkH5d#wKjc%en7jf_AN~x z8rjTwKA9x5EBv* z5Pw&E*U$!`|H9>;&!SL)?ob7R zogHm|ByueRIq$Q`3qUhVKu1mbRnNdR+gs(|3V|z2w&|6RCa&0 zYr8$q`vK16kILu=gRNOqZ{(8!r6Ew}-Yhr!eu?pSq4jey(_t~{^#M!q1IZVF{?@X+ zYx;l1*Pn&k&+)DSzRLMG-nE>n%fG@ivbD7XH21@p?b-}hgv(c#0A@a5YyNBSzgo6; z%<}KxDcXUr*|o6*0?XE1##Y4M{^yJXZwTI>0pOJYp?iN3xxQq3zgzhoLNeyIKurrA z4kYqT8Ziry$uFw>oLDod697F8pazun-&(f!y|v$=_NNJSj2_x3+1mVdvc!LmIgTG<>@YcC07At9 zsMT*)J>boMCv-i$0K@@@mf*hzSwGtCngDA2-6RWOSXc)0eFq?V z^&9;D>4QIq&M0V5fB<~^I-0&q(ZF=lch$##*bK! ziG1%EH*<~boqrTRXf+~Ui0fKy#LGw*TQYyjhvkU zN|3>Of3Dd0N;%tmQ|)&M-{_bBQN)f<^IfljfaSgm0ip4g3~IyYF*v-}+vzLmCL_5AG@f2iSF+kZIfzc{F=06OIrpow)r6N)$3 zfVce}HvXtHzYZ(Dvht0Cf3|nfmN&%-H3WopX_s0 zL8Sv=-!8z_ZhE?0=I;=`nJ_3sY-~iFud7lMOZz{^0DpSff4-UjKCCb7h2x$ANj4l{ z0vQxnySdbDQV8I%6{eW#cYTit^mBIacoPkiB-|KVBRB zV6H`%CLz>+mk#3EdE&!)HMD zyqQYQ;%+1Ni|R$dU3ls4YqV4z=$} zAmGK(A0xlt7| z8%OVVsQjI5eo5xKs~Ke$fMx-@npHrJQeSBR*KF^cr@uu;NnQDCs#gc@|9tiMFP`G> zoYoEkh+6z$JHG!{+I4_ORb~GG0uoR`2p)DPxsB}dIM2a9P22e!tqeyQ8{(tAqn>X*idWZk}zWK5ryL*18-*eAB z@4g8TrvJG)k6`JR(6^1@V5Xag1HEiyQT-RfnUD!bmg)*I-^=N145r!smT>Ww!&N3=EaOkFN!Yb zh{JRCj-S5E2?cCGf{103Lz`+mEU0n-6E3z;hfVljXdC&5vw8Dw0C*kNAa;9Mp#~4Q zwB&d}rGJoZvGkTAI-j}t*3Tb)3kK#PSD?a>9=9vIwh;q#0-+X(Oqv|L=yVlO>;sCy z5P%Ouob=)&h|b(4vA=jZuZ$d&;ep*%K;-VHQDLPJYA zf~XdSVtv}h5@jyKHLe5XRYsKS79#+ib_wx3xx!Bvv__u7)}bXsvLY%1{XB(zk$CCWE}}5Pxz~*#;YUu5!#tXX zIGHvk+XkBkL0KV;6l*?Ek@O}ocqB$Y>`qE*2~dFaDSP7r_8Bc31q3t{Y0)3IYpE`c=-3C8L^x8gEbE+#l}!yUeg}Eog}m--XcE8IH|dcN&(F=v&hmh$ znO16&J%qJvS5dV}U2JBCf#|F*zUqricyKQ$fNhKpGNj{cj9Zc%nP^XYTI1TxW;|$m ztTRG<&0OWYYrP#t%=$pI>8=@CG@efEKL+z5j4Q0dJE4jYlqtbS*Ai}ZP;(uJTGVIH zHIm|+>?*(g2WWeM<{O+MLoUC^4f-p&0K;hokt6 z6lF8a8RR@bjFW*ic5?!u!fVG zK#3v+f8g z-;aPpHD>4zg+>%Bw8ti%V-LNJ8tL-13)CTMU>)Mh8$E0#mDyQE zP0HEq5i@ZW8oZ#wT=@4Q5M$des}1Q03v*(+JvFZ@-mgkQ4l?ocVPnU=*$+Tt0MwSz z>6{QFhI{8{o%t^1;QC1puZzMZdaEuBZNd}f&A-*CUV?##ulyw z3RN5I;@q~Y5sGX8N&G%y1m2<4aqN1h?hRM~kYfO0H-gP0jX;tdR4?LfLiF4W5A zv6~z1ZwrXpfM91wYjjsZcsF?Rcm8Pn*#LtqLM8xt;i)D5^-M)9k5XZzq6EvwC*?D( z?2tJTdRV^r;_Jw8k;wARB|?-peljK$5ivM2A~Z26J|aOo)Q{DCx{%tebSBH1vto=P zi;*67Bf+lW?5R=@zEny}-K2O!5W9EnPzUaTR#9hK!G1W63y5S?@-;V2vB-|b`(=(? zJJO+vJ+Dn{y0t&>u0dfe=HNtLIYhmoR)}y|lQRwdlyo;Bc>93skZ&|(WijjY<;9H2 z_K6sY9g|UP5N3GGcXIU+(v zI|_5o&QxoP##;hPcVok@(@+&idR|^mynT2+=BHJKRYCPtR~6LHk?7q7U&4VZ8T7fgy`B%3)R;@=^}o(Z-0c_`0?>TF|`?mncpuLF;U;8#Rgp zjSHjRjt+BR!hp^gYhVk-$M14r2nOhdo~&lWhpakr%ciA$pssKT!fxcZj`I`1k`y1^ zO{XubbRf1gPoAKHLeGa75{JBlRC#vV?8N3<=W=aXfm)T01ZzeDt?Ut$%=jjOwZkp3W4Ia>M8X&{ zH77%r1L^zCv1HP~y;F$`Ed|%f;(=gsBVJW$4x=#Q>QC$j)B9 z9WRtl6fN?EBv9}`IBB~X+p+B6@PG#wJW z1qpDhCp~Ug3J5*?MX=)4{Rm~ zR?jgZop)k)p-TQ6Q*I`$0`E~Mg4tucFuQ-0aV+Tx6xk_&u0Wgi#r{!<1h5C&gHXvB zltp74{=v%6Cw1X6%Z=DQ3r(J_)s=mOssHv5j$g!n<$k_*6Tw*@^;{Su;+P5kVR}xh z#UmhH85_30v*I&}X2ZQO+>7ra1zhOdoNOB0eVte})5A&de=49YNjILeQ zg{Zjq?5Yb8WH(9?79#&B4}`BtpKoo_!y407Scx6;2h;bTXu$t<9m4`YN1vpH^#UQ#6b(V&t z|Bbdm87w|5#I74W5D9jxGbKF{-`v!N*u8&apB5m$0xLKRQR)s4grFm1Gt_=iv1%3+ zRR^)bnqkUcJP>$yE;CzKfMeD}J7*&amPPr*=G?ADN*L!{AsnO(bGfzrw$BeJ2mhMy=_Td6-T#HFsnp4Yyz|8s1} z_G1^oo@!ic!z}}Yr3=;a*`q&?L$cd&2NvodA>2^$_7NCK6le2wp{|VRn9&~rL)W^p zZqO!z8%mOjuEWHgQ+nBK?y`jy?*ObbR>>Y-FTh{iIfZJ*uweDMfp9NgfdolLGZV5@ zlWvz%;p;bHzMc$jcmg-B+*3qAY9A54uE2!l-E=gSfXVMG_A>!yUSpOw`Kj_RvE;30 zPAsEGswq1-1N@>Z&E=64iP@g{b6sg(ekb4g4$OB@qh#6Or-{{f736<=2oniFb5*;LGjIYhn?g61@dT9 zA8SzWK6hv@aTFogl`&`X6o0T-b3!~_Z+)e1?dD~HHmRPnrE%tce-Oj0xpo#+`()*8 zTW9j?NZ51HwqcV@=t6(+$ZQ(!qOaPxKYA_;fNCE?SoWpY!d3nNvqEaHpsVV^b1rn9 z4@deDu@cM}H+H>09C%{I$f!izn^1ivBqh|4ZT=wT(M=3^2P}`hql0p5+e;fSltD!N z7YTa+Bh1_vgldc+a8I~r1e}(dNN0AICu3;GlHAN}*Jsli6Wl!9oUiKo)Su!W zvjaWYwX)u1-8A#1L!H*e4^4pLw zSp0kePRstG$NmB7Ldcz~nWQ?(As-hlY3$T?x|lgfyz*Yq+|S=fG`iyH$WCvSEoBNlj#lQ#Jj<@mzU= zmzG4mVH8fhw_MjmRZj<=Pl10o#lQxO9n;V+?5HgIh6(ADUd-`w&>bUIy4bazM%8$V zT#E39&1e`Wm2qV^)P`mSfWQws@?Y%Tyv(Xi@3uNs)kW^_S*ke z6$iv~tQa8@x8s& z4hPkZ_>eVT&M<-TCOx{_jY)h&*V_Bt_nV?*s@z!FJ&gB@uAxN+?ObtkC(7KZ&=b2` zIW^KB<~?0Rx~)NbeIPCU{i$E+IH?U1%#kw}$ zA1E=~kX*W0la2*nm<{#yft#_bc={}Vu;A9l`aAvchuMpuyVb~Mto{=4`kP@6WgN{@ zrx?maPVC(gwDP*NFuvMjoe7kXt}zQLeP%)B*mE2G5qKSb&>Ahq2#Gjlfe-yBp5zd) z3{nC4LtJ|Q?RSuPreoPhZ+;o}=tzDN+3P^BO7c@uY=Nzh zp|9p58RB$Pm(IN{ofIJ2Nt%*L6Mm!!vXMvYD4z3{KS1)m|#>SBz7t(IM8w>oUO>DFQT$$ZTljRK~k zk}HON0Nc4=R)!Nqlq24rCfwwnrw=yfSx>5d1J*+#)kh71G6Qc!;oL-9yk1B!80+^%gf6Hk=9sSg?# zA4+|rf>mU5J>P&Fru@_y*==HzXRo%$UM&raP+vq z?~3`?xAV}4datc=C^D>%A+x@bMNQgdvCBX2D;tf?#6@K5<_!N9%t|W!PObrVeIhj&&9NjJXZzP;Bi9YE*XS^W- zqZdnp_j8}pM?N(qBD_JLlpNR?c(_?c;oVb-;5`Lj;a9^WcaxF5r%oVMrEXKe%$+#BhQylN1t zy-;4?LDgBIOr!yb6_=B61gd<&^pwk;#IKsaa-J`H2=_7s_->4cb% zNBXJ^=Yr@m3YwyP1&{RhIBfLC$l;PQ* z#^j@Z6|V0#0q)Rm?8WD>z%zv7KuHx8{_h{<1=d!EXWQVp+=fdBB};E;Py}|V3cI8* ztduA((~#or9ON`#h=ESs-{Y7|KYL2 zHVlU@_anj^OT3aUZ=xToa4E&wZ;92o*h2WcVd=sPG3ZEr3nxVlDLrnN`>u*;FHBBo z^R_=^;DF<>aVUBSY)Kzfp_Gz0=KiC&_}K7>goMEfiJ^%RYT`W0hXCgta-FT>S@H!^e9|Udw+dVn;P9YN7Gi=k?o=V|x6Y5^pMPumfvq^;K zrH(e62ZyJNnZg;1mwVR)gi(^kckXCx6~X|P@c0%d^41b#OaNt}rrfJUCWN%C3o*XI zFIuEdfAGSZA5;a2!qcseG&|jwhpG6tO(S{pqu_u@ptgcgeHrBM#`4RPgoD!@I=Hpt zGi9Fd%Zs+dz-5*-&m?WNI1;h29+=pVUpIz{S>yPb=jUsd{NfnUDiioY;Vt$&ugp@SHojSu9CuB-WxtZh7x81GW938>bT68yVS~-|yngoK zxd6b23=|?(n`{EYMu*nPxX`SJ`1sU=Iss6bu)vufS{~>}@naXGNAIc7K}gjl;J5@@ zxlpr}-4L`}6*Z2|AvMwIl{D|Gm%%HQ-|WHO0z*2Y)uk^4@$86~Ty(!S3pgLaGVFoF z^Jzvr`$o}lMK+CYC*g)Aqg5oN>7-e8v8=v{;Kr&bJ#N?48GJx$<0_TT_Gx0)tLWy4 zIYIL*dU}V!A9RB%EKmjOCR5EQ$;etFGwhi*&&;qk$M4M_+paV)zE{;oK^sjtmf?8D z$)ao&eA+lEBd3x!LNr%oc_37O+Vr$DmO{5j9e@?Dd{E) z&a=JEd;c4leIXIM-frK)jT?na&YezNP)R2w!aXjeK@;SsHSTV|1qqh`y*DHA{9WA0 zvhIqX%Y97o8Yf2*Gz4cA{$MvZe8k8c)UlrH?dg(U4zV{pIdB7C@eQa7+^&q+-+jW3 zOZ~<-+6s5eqeM4R^!^H)3u^*#6Mmr#;

7hzLFUQa~gsDHFI4ug0Hm2q?PAiS?+L z_Ho0JBT-iqCqP(n^YCS9>S~%GFE9Jr_ytgF9JbPI{+zYn7};A$P+ImTfCQ`7a!wh) zbI(ePM?<>y$ZPEKIQ|enj>QwLBqh0x#*qbK6<^ip0+PFtpben0g|XUKCO zM@lS4tNZ*IULVlLh~5!ys}K5-m@Rmd(c8gC{1~!zP5XpQSzH84P_)(r|E+?u(}*v+ zllntJpAPC)r6`VeKgUU7dx;#aGVk(`6G~z97bO5~{md*3`gkDxsxD%&8G~}R>_3!c zsG4bteQm;hZIhpT1LNA^KHRqX3G1c+;JFFZaGNbf5vaUj{HF0^ugn{Z&n z4chHo$B*4*-t>>KN*F)3TxZ`rwvaY_r|@HEmc~`_OFDrG@mRy-7lzwx>ce&%bl_qt zREsOy6v6XKH5ImSV(~(slu-sxHPM)!n~_h{P(kJD3`EiY&+7^7ph1b6x10@nZM)5C#6upRsuHgA)6Z_p<{!#*Lx3iTX=Ehnal1YU;z7`-+^p)(gkM_*1k{?Y zRQ9B1x|GHF8*OD1b6!92nU6BUhW@$~anrWbo|`T-2vj}VDG&Ng!B@Bg=0o~^lI-LV zw>T+^GhG6C@5fx^|HjpFdOPRC0gJ&xB8GI>4S3gqe1gd#Es6c&bPghdt9M%;hs9?V zx`k}#zoXTEAkBdoinHX36SJ1!Af*TP`fLU8bGH95+Rm_e@P7~EgNXrjB?P;9_;!^4 z7#6t##e3_uaUIVl=Pm|rZB!p@dywv5P&9bF8Ou4*cff3O9$)RDi0)l#!BJeoiT~3;{$SY$YinT z+{7z4Ios1(v0fJ=13NTTlB+vm>TdePQZ77}-IHA~p(JKwGAcFBaY)`hJ5%#hY83S! zTyZS>1IctYw89Qz)!A&!u?$CB;lnCS??KD#6a0L&Am!=aTa8Hz?l1Ss;?P1sz?33W Piac0^x#Vpq2Lk>Nfn;V+ diff --git a/jar/core-8.0.0.jar b/jar/core-8.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..fc8067d57a5ab700f73f7ed9616c4fd1de233fb6 GIT binary patch literal 747495 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

U)(Ym;5!7M(X&IP8q2fgCVr}dh&&T2cJqASCst@ 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 z

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%0DOwzVAfBj&3@ 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

-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 }z2IN>AJftl?v03kH*?OYUty}gcAn+$ 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℘+|BR{W-wd7~i| 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 literal 0 HcmV?d00001 diff --git a/src/main/java/io/supertokens/ActiveUsers.java b/src/main/java/io/supertokens/ActiveUsers.java index 7c4601958..7ee3c5534 100644 --- a/src/main/java/io/supertokens/ActiveUsers.java +++ b/src/main/java/io/supertokens/ActiveUsers.java @@ -1,19 +1,22 @@ package io.supertokens; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import org.jetbrains.annotations.TestOnly; public class ActiveUsers { - public static void updateLastActive(AppIdentifierWithStorage appIdentifierWithStorage, Main main, String userId) + public static void updateLastActive(AppIdentifier appIdentifier, Main main, String userId) throws TenantOrAppNotFoundException { + Storage storage = StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main); try { - appIdentifierWithStorage.getActiveUsersStorage().updateLastActive(appIdentifierWithStorage, userId); + StorageUtils.getActiveUsersStorage(storage).updateLastActive(appIdentifier, userId); } catch (StorageQueryException ignored) { } } @@ -21,36 +24,22 @@ public static void updateLastActive(AppIdentifierWithStorage appIdentifierWithSt @TestOnly public static void updateLastActive(Main main, String userId) { try { - ActiveUsers.updateLastActive(new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(main)), main, - userId); + ActiveUsers.updateLastActive(new AppIdentifier(null, null), + main, userId); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static int countUsersActiveSince(AppIdentifierWithStorage appIdentifierWithStorage, Main main, long time) + public static int countUsersActiveSince(Main main, AppIdentifier appIdentifier, long time) throws StorageQueryException, TenantOrAppNotFoundException { - return appIdentifierWithStorage.getActiveUsersStorage().countUsersActiveSince(appIdentifierWithStorage, time); + Storage storage = StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main); + return StorageUtils.getActiveUsersStorage(storage).countUsersActiveSince(appIdentifier, time); } @TestOnly public static int countUsersActiveSince(Main main, long time) throws StorageQueryException, TenantOrAppNotFoundException { - return countUsersActiveSince(new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(main)), main, - time); - } - - public static void removeActiveUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId) - throws StorageQueryException { - try { - ((AuthRecipeSQLStorage) appIdentifierWithStorage.getActiveUsersStorage()).startTransaction(con -> { - appIdentifierWithStorage.getActiveUsersStorage().deleteUserActive_Transaction(con, appIdentifierWithStorage, userId); - ((AuthRecipeSQLStorage) appIdentifierWithStorage.getActiveUsersStorage()).commitTransaction(con); - return null; - }); - - } catch (StorageTransactionLogicException e) { - throw new StorageQueryException(e.actualException); - } + return countUsersActiveSince(main, new AppIdentifier(null, null), time); } } diff --git a/src/main/java/io/supertokens/AppIdentifierWithStorageAndUserIdMapping.java b/src/main/java/io/supertokens/StorageAndUserIdMapping.java similarity index 69% rename from src/main/java/io/supertokens/AppIdentifierWithStorageAndUserIdMapping.java rename to src/main/java/io/supertokens/StorageAndUserIdMapping.java index 42760c500..f4cf990de 100644 --- a/src/main/java/io/supertokens/AppIdentifierWithStorageAndUserIdMapping.java +++ b/src/main/java/io/supertokens/StorageAndUserIdMapping.java @@ -16,23 +16,21 @@ package io.supertokens; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class AppIdentifierWithStorageAndUserIdMapping { +public class StorageAndUserIdMapping { @Nullable public final io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping; @Nonnull - public final AppIdentifierWithStorage appIdentifierWithStorage; + public final Storage storage; - public AppIdentifierWithStorageAndUserIdMapping(AppIdentifierWithStorage appIdentifierWithStorage, UserIdMapping userIdMapping) { - this.appIdentifierWithStorage = appIdentifierWithStorage; + public StorageAndUserIdMapping(Storage storage, UserIdMapping userIdMapping) { + this.storage = storage; this.userIdMapping = userIdMapping; - - assert(this.appIdentifierWithStorage != null); } } diff --git a/src/main/java/io/supertokens/TenantIdentifierWithStorageAndUserIdMapping.java b/src/main/java/io/supertokens/TenantIdentifierWithStorageAndUserIdMapping.java deleted file mode 100644 index 156ec77f3..000000000 --- a/src/main/java/io/supertokens/TenantIdentifierWithStorageAndUserIdMapping.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2023, 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; - -import io.supertokens.pluginInterface.Storage; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; -import io.supertokens.pluginInterface.useridmapping.UserIdMapping; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class TenantIdentifierWithStorageAndUserIdMapping { - @Nullable - public final io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping; - - @Nonnull - public final TenantIdentifierWithStorage tenantIdentifierWithStorage; - - public TenantIdentifierWithStorageAndUserIdMapping(TenantIdentifierWithStorage tenantIdentifierWithStorage, - UserIdMapping userIdMapping) { - this.tenantIdentifierWithStorage = tenantIdentifierWithStorage; - this.userIdMapping = userIdMapping; - - assert(this.tenantIdentifierWithStorage != null); - } -} diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 1c3a2621a..7d4b394fc 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -28,6 +28,7 @@ import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; @@ -36,9 +37,8 @@ import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage; import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; @@ -60,21 +60,19 @@ public class AuthRecipe { @TestOnly public static boolean unlinkAccounts(Main main, String recipeUserId) throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException { - AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, - StorageLayer.getStorage(main)); - return unlinkAccounts(main, appId, recipeUserId); + return unlinkAccounts(main, new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId); } // returns true if the input user ID was deleted - which can happens if it was a primary user id and // there were other accounts linked to it as well. - public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIdentifierWithStorage, - String recipeUserId) + public static boolean unlinkAccounts(Main main, AppIdentifier appIdentifier, + Storage storage, String recipeUserId) throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException { - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); try { - UnlinkResult res = storage.startTransaction(con -> { - AuthRecipeUserInfo primaryUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + UnlinkResult res = authRecipeStorage.startTransaction(con -> { + AuthRecipeUserInfo primaryUser = authRecipeStorage.getPrimaryUserById_Transaction(appIdentifier, con, recipeUserId); if (primaryUser == null) { throw new StorageTransactionLogicException(new UnknownUserIdException()); @@ -86,13 +84,13 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - appIdentifierWithStorage, + appIdentifier, authRecipeStorage, recipeUserId, UserIdType.SUPERTOKENS); if (primaryUser.getSupertokensUserId().equals(recipeUserId)) { // we are trying to unlink the user ID which is the same as the primary one. if (primaryUser.loginMethods.length == 1) { - storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.getSupertokensUserId(), recipeUserId); + authRecipeStorage.unlinkAccounts_Transaction(appIdentifier, con, primaryUser.getSupertokensUserId(), recipeUserId); return new UnlinkResult(mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); } else { // Here we delete the recipe user id cause if we just unlink, then there will be two @@ -100,15 +98,15 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden // The delete will also cause the automatic unlinking. // We need to make sure that it only deletes sessions for recipeUserId and not other linked // users who have their sessions for primaryUserId (that is equal to the recipeUserId) - deleteUserHelper(con, appIdentifierWithStorage, recipeUserId, false, mappingResult); + deleteUserHelper(con, appIdentifier, storage, recipeUserId, false, mappingResult); return new UnlinkResult(mappingResult == null ? recipeUserId : mappingResult.externalUserId, true); } } else { - storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.getSupertokensUserId(), recipeUserId); + authRecipeStorage.unlinkAccounts_Transaction(appIdentifier, con, primaryUser.getSupertokensUserId(), recipeUserId); return new UnlinkResult(mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); } }); - Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, res.userId, false); + Session.revokeAllSessionsForUser(main, appIdentifier, storage, res.userId, false); return res.wasLinked; } catch (StorageTransactionLogicException e) { if (e.actualException instanceof UnknownUserIdException) { @@ -123,14 +121,12 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden @TestOnly public static AuthRecipeUserInfo getUserById(Main main, String userId) throws StorageQueryException { - AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, - StorageLayer.getStorage(main)); - return getUserById(appId, userId); + return getUserById(new AppIdentifier(null, null), StorageLayer.getStorage(main), userId); } - public static AuthRecipeUserInfo getUserById(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static AuthRecipeUserInfo getUserById(AppIdentifier appIdentifier, Storage storage, String userId) throws StorageQueryException { - return appIdentifierWithStorage.getAuthRecipeStorage().getPrimaryUserById(appIdentifierWithStorage, userId); + return StorageUtils.getAuthRecipeStorage(storage).getPrimaryUserById(appIdentifier, userId); } public static class CreatePrimaryUserResult { @@ -161,24 +157,22 @@ public static CanLinkAccountsResult canLinkAccounts(Main main, String recipeUser throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException, RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException { - AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, - StorageLayer.getStorage(main)); - return canLinkAccounts(appId, recipeUserId, primaryUserId); + return canLinkAccounts(new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId, primaryUserId); } - public static CanLinkAccountsResult canLinkAccounts(AppIdentifierWithStorage appIdentifierWithStorage, + public static CanLinkAccountsResult canLinkAccounts(AppIdentifier appIdentifier, Storage storage, String recipeUserId, String primaryUserId) throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException, RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException { - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); try { - return storage.startTransaction(con -> { + return authRecipeStorage.startTransaction(con -> { try { - CanLinkAccountsResult result = canLinkAccountsHelper(con, appIdentifierWithStorage, + CanLinkAccountsResult result = canLinkAccountsHelper(con, appIdentifier, authRecipeStorage, recipeUserId, primaryUserId); - storage.commitTransaction(con); + authRecipeStorage.commitTransaction(con); return result; } catch (UnknownUserIdException | InputUserIdIsNotAPrimaryUserException | @@ -202,13 +196,14 @@ public static CanLinkAccountsResult canLinkAccounts(AppIdentifierWithStorage app } private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection con, - AppIdentifierWithStorage appIdentifierWithStorage, + AppIdentifier appIdentifier, + Storage storage, String _recipeUserId, String _primaryUserId) throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException, RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException { - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); - AuthRecipeUserInfo primaryUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); + AuthRecipeUserInfo primaryUser = authRecipeStorage.getPrimaryUserById_Transaction(appIdentifier, con, _primaryUserId); if (primaryUser == null) { @@ -219,7 +214,7 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection throw new InputUserIdIsNotAPrimaryUserException(primaryUser.getSupertokensUserId()); } - AuthRecipeUserInfo recipeUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + AuthRecipeUserInfo recipeUser = authRecipeStorage.getPrimaryUserById_Transaction(appIdentifier, con, _recipeUserId); if (recipeUser == null) { throw new UnknownUserIdException(); @@ -255,15 +250,15 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection // do the checks in both. for (String tenantId : tenantIds) { TenantIdentifier tenantIdentifier = new TenantIdentifier( - appIdentifierWithStorage.getConnectionUriDomain(), appIdentifierWithStorage.getAppId(), + appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), tenantId); - // we do not bother with getting the tenantIdentifierWithStorage here because + // we do not bother with getting the storage for each tenant here because // we get the tenants from the user itself, and the user can only be shared across // tenants of the same storage - therefore, the storage will be the same. if (recipeUserIdLM.email != null) { - AuthRecipeUserInfo[] usersWithSameEmail = storage - .listPrimaryUsersByEmail_Transaction(appIdentifierWithStorage, con, + AuthRecipeUserInfo[] usersWithSameEmail = authRecipeStorage + .listPrimaryUsersByEmail_Transaction(appIdentifier, con, recipeUserIdLM.email); for (AuthRecipeUserInfo user : usersWithSameEmail) { if (!user.tenantIds.contains(tenantId)) { @@ -277,8 +272,8 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection } if (recipeUserIdLM.phoneNumber != null) { - AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage - .listPrimaryUsersByPhoneNumber_Transaction(appIdentifierWithStorage, con, + AuthRecipeUserInfo[] usersWithSamePhoneNumber = authRecipeStorage + .listPrimaryUsersByPhoneNumber_Transaction(appIdentifier, con, recipeUserIdLM.phoneNumber); for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { if (!user.tenantIds.contains(tenantId)) { @@ -293,8 +288,8 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection } if (recipeUserIdLM.thirdParty != null) { - AuthRecipeUserInfo[] usersWithSameThirdParty = storage - .listPrimaryUsersByThirdPartyInfo_Transaction(appIdentifierWithStorage, con, + AuthRecipeUserInfo[] usersWithSameThirdParty = authRecipeStorage + .listPrimaryUsersByThirdPartyInfo_Transaction(appIdentifier, con, recipeUserIdLM.thirdParty.id, recipeUserIdLM.thirdParty.userId); for (AuthRecipeUserInfo userWithSameThirdParty : usersWithSameThirdParty) { if (!userWithSameThirdParty.tenantIds.contains(tenantId)) { @@ -321,46 +316,45 @@ public static LinkAccountsResult linkAccounts(Main main, String recipeUserId, St UnknownUserIdException, FeatureNotEnabledException, InputUserIdIsNotAPrimaryUserException, RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException { - AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, - StorageLayer.getStorage(main)); try { - return linkAccounts(main, appId, recipeUserId, primaryUserId); + return linkAccounts(main, new AppIdentifier(null, null), + StorageLayer.getStorage(main), recipeUserId, primaryUserId); } catch (TenantOrAppNotFoundException e) { throw new RuntimeException(e); } } - public static LinkAccountsResult linkAccounts(Main main, AppIdentifierWithStorage appIdentifierWithStorage, - String _recipeUserId, String _primaryUserId) + public static LinkAccountsResult linkAccounts(Main main, AppIdentifier appIdentifier, + Storage storage, String _recipeUserId, String _primaryUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, InputUserIdIsNotAPrimaryUserException, UnknownUserIdException, TenantOrAppNotFoundException, FeatureNotEnabledException { - if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifierWithStorage).getEnabledFeatures()) + if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures()) .noneMatch(t -> (t == EE_FEATURES.ACCOUNT_LINKING || t == EE_FEATURES.MFA))) { throw new FeatureNotEnabledException( "Account linking feature is not enabled for this app. Please contact support to enable it."); } - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); try { - LinkAccountsResult result = storage.startTransaction(con -> { + LinkAccountsResult result = authRecipeStorage.startTransaction(con -> { try { - CanLinkAccountsResult canLinkAccounts = canLinkAccountsHelper(con, appIdentifierWithStorage, - _recipeUserId, _primaryUserId); + CanLinkAccountsResult canLinkAccounts = canLinkAccountsHelper(con, appIdentifier, + authRecipeStorage, _recipeUserId, _primaryUserId); if (canLinkAccounts.alreadyLinked) { - return new LinkAccountsResult(getUserById(appIdentifierWithStorage, canLinkAccounts.primaryUserId), true); + return new LinkAccountsResult(getUserById(appIdentifier, authRecipeStorage, canLinkAccounts.primaryUserId), true); } // now we can link accounts in the db. - storage.linkAccounts_Transaction(appIdentifierWithStorage, con, canLinkAccounts.recipeUserId, + authRecipeStorage.linkAccounts_Transaction(appIdentifier, con, canLinkAccounts.recipeUserId, canLinkAccounts.primaryUserId); - storage.commitTransaction(con); + authRecipeStorage.commitTransaction(con); - return new LinkAccountsResult(getUserById(appIdentifierWithStorage, canLinkAccounts.primaryUserId), false); + return new LinkAccountsResult(getUserById(appIdentifier, authRecipeStorage, canLinkAccounts.primaryUserId), false); } catch (UnknownUserIdException | InputUserIdIsNotAPrimaryUserException | RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException | AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { @@ -371,10 +365,10 @@ public static LinkAccountsResult linkAccounts(Main main, AppIdentifierWithStorag if (!result.wasAlreadyLinked) { io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - appIdentifierWithStorage, + appIdentifier, authRecipeStorage, _recipeUserId, UserIdType.SUPERTOKENS); // finally, we revoke all sessions of the recipeUser Id cause their user ID has changed. - Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, + Session.revokeAllSessionsForUser(main, appIdentifier, authRecipeStorage, mappingResult == null ? _recipeUserId : mappingResult.externalUserId, false); } @@ -408,21 +402,20 @@ public static CreatePrimaryUserResult canCreatePrimaryUser(Main main, String recipeUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException { - AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, - StorageLayer.getStorage(main)); - return canCreatePrimaryUser(appId, recipeUserId); + return canCreatePrimaryUser(new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId); } - public static CreatePrimaryUserResult canCreatePrimaryUser(AppIdentifierWithStorage appIdentifierWithStorage, + public static CreatePrimaryUserResult canCreatePrimaryUser(AppIdentifier appIdentifier, + Storage storage, String recipeUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException { - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); try { - return storage.startTransaction(con -> { + return authRecipeStorage.startTransaction(con -> { try { - return canCreatePrimaryUserHelper(con, appIdentifierWithStorage, + return canCreatePrimaryUserHelper(con, appIdentifier, storage, recipeUserId); } catch (UnknownUserIdException | RecipeUserIdAlreadyLinkedWithPrimaryUserIdException | @@ -443,13 +436,14 @@ public static CreatePrimaryUserResult canCreatePrimaryUser(AppIdentifierWithStor } private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionConnection con, - AppIdentifierWithStorage appIdentifierWithStorage, + AppIdentifier appIdentifier, + Storage storage, String recipeUserId) throws StorageQueryException, UnknownUserIdException, RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException { - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); - AuthRecipeUserInfo targetUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + AuthRecipeUserInfo targetUser = authRecipeStorage.getPrimaryUserById_Transaction(appIdentifier, con, recipeUserId); if (targetUser == null) { throw new UnknownUserIdException(); @@ -470,8 +464,8 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon for (String tenantId : targetUser.tenantIds) { if (loginMethod.email != null) { - AuthRecipeUserInfo[] usersWithSameEmail = storage - .listPrimaryUsersByEmail_Transaction(appIdentifierWithStorage, con, + AuthRecipeUserInfo[] usersWithSameEmail = authRecipeStorage + .listPrimaryUsersByEmail_Transaction(appIdentifier, con, loginMethod.email); for (AuthRecipeUserInfo user : usersWithSameEmail) { if (!user.tenantIds.contains(tenantId)) { @@ -485,8 +479,8 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon } if (loginMethod.phoneNumber != null) { - AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage - .listPrimaryUsersByPhoneNumber_Transaction(appIdentifierWithStorage, con, + AuthRecipeUserInfo[] usersWithSamePhoneNumber = authRecipeStorage + .listPrimaryUsersByPhoneNumber_Transaction(appIdentifier, con, loginMethod.phoneNumber); for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { if (!user.tenantIds.contains(tenantId)) { @@ -501,8 +495,8 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon } if (loginMethod.thirdParty != null) { - AuthRecipeUserInfo[] usersWithSameThirdParty = storage - .listPrimaryUsersByThirdPartyInfo_Transaction(appIdentifierWithStorage, con, + AuthRecipeUserInfo[] usersWithSameThirdParty = authRecipeStorage + .listPrimaryUsersByThirdPartyInfo_Transaction(appIdentifier, con, loginMethod.thirdParty.id, loginMethod.thirdParty.userId); for (AuthRecipeUserInfo userWithSameThirdParty : usersWithSameThirdParty) { if (!userWithSameThirdParty.tenantIds.contains(tenantId)) { @@ -527,41 +521,40 @@ public static CreatePrimaryUserResult createPrimaryUser(Main main, throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException, FeatureNotEnabledException { - AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, - StorageLayer.getStorage(main)); try { - return createPrimaryUser(main, appId, recipeUserId); + return createPrimaryUser(main, new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId); } catch (TenantOrAppNotFoundException e) { throw new RuntimeException(e); } } public static CreatePrimaryUserResult createPrimaryUser(Main main, - AppIdentifierWithStorage appIdentifierWithStorage, + AppIdentifier appIdentifier, + Storage storage, String recipeUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException, TenantOrAppNotFoundException, FeatureNotEnabledException { - if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifierWithStorage).getEnabledFeatures()) + if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures()) .noneMatch(t -> (t == EE_FEATURES.ACCOUNT_LINKING || t == EE_FEATURES.MFA))) { throw new FeatureNotEnabledException( "Account linking feature is not enabled for this app. Please contact support to enable it."); } - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); try { - return storage.startTransaction(con -> { + return authRecipeStorage.startTransaction(con -> { try { - CreatePrimaryUserResult result = canCreatePrimaryUserHelper(con, appIdentifierWithStorage, + CreatePrimaryUserResult result = canCreatePrimaryUserHelper(con, appIdentifier, authRecipeStorage, recipeUserId); if (result.wasAlreadyAPrimaryUser) { return result; } - storage.makePrimaryUser_Transaction(appIdentifierWithStorage, con, result.user.getSupertokensUserId()); + authRecipeStorage.makePrimaryUser_Transaction(appIdentifier, con, result.user.getSupertokensUserId()); - storage.commitTransaction(con); + authRecipeStorage.commitTransaction(con); result.user.isPrimaryUser = true; @@ -583,7 +576,8 @@ public static CreatePrimaryUserResult createPrimaryUser(Main main, } } - public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifierWithStorage tenantIdentifier, + public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifier tenantIdentifier, + Storage storage, boolean doUnionOfAccountInfo, String email, String phoneNumber, String thirdPartyId, String thirdPartyUserId) @@ -591,17 +585,17 @@ public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifierWithSto Set result = new HashSet<>(); if (email != null) { - AuthRecipeUserInfo[] users = tenantIdentifier.getAuthRecipeStorage() + AuthRecipeUserInfo[] users = StorageUtils.getAuthRecipeStorage(storage) .listPrimaryUsersByEmail(tenantIdentifier, email); result.addAll(List.of(users)); } if (phoneNumber != null) { - AuthRecipeUserInfo[] users = tenantIdentifier.getAuthRecipeStorage() + AuthRecipeUserInfo[] users = StorageUtils.getAuthRecipeStorage(storage) .listPrimaryUsersByPhoneNumber(tenantIdentifier, phoneNumber); result.addAll(List.of(users)); } if (thirdPartyId != null && thirdPartyUserId != null) { - AuthRecipeUserInfo user = tenantIdentifier.getAuthRecipeStorage() + AuthRecipeUserInfo user = StorageUtils.getAuthRecipeStorage(storage) .getPrimaryUserByThirdPartyInfo(tenantIdentifier, thirdPartyId, thirdPartyUserId); if (user != null) { result.add(user); @@ -653,28 +647,25 @@ public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifierWithSto } - public static long getUsersCountForTenant(TenantIdentifierWithStorage tenantIdentifier, + public static long getUsersCountForTenant(TenantIdentifier tenantIdentifier, + Storage storage, RECIPE_ID[] includeRecipeIds) throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException { - return tenantIdentifier.getAuthRecipeStorage().getUsersCount( + return StorageUtils.getAuthRecipeStorage(storage).getUsersCount( tenantIdentifier, includeRecipeIds); } - public static long getUsersCountAcrossAllTenants(AppIdentifierWithStorage appIdentifierWithStorage, + public static long getUsersCountAcrossAllTenants(AppIdentifier appIdentifier, + Storage[] storages, RECIPE_ID[] includeRecipeIds) throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException { long count = 0; - for (Storage storage : appIdentifierWithStorage.getStorages()) { - if (storage.getType() != STORAGE_TYPE.SQL) { - // we only support SQL for now - throw new UnsupportedOperationException(""); - } - - count += ((AuthRecipeStorage) storage).getUsersCount( - appIdentifierWithStorage, includeRecipeIds); + for (Storage storage : storages) { + count += StorageUtils.getAuthRecipeStorage(storage).getUsersCount( + appIdentifier, includeRecipeIds); } return count; @@ -685,15 +676,14 @@ public static long getUsersCount(Main main, RECIPE_ID[] includeRecipeIds) throws StorageQueryException { try { Storage storage = StorageLayer.getStorage(main); - return getUsersCountForTenant(new TenantIdentifierWithStorage( - null, null, null, storage), - includeRecipeIds); + return getUsersCountForTenant(TenantIdentifier.BASE_TENANT, storage, includeRecipeIds); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } - public static UserPaginationContainer getUsers(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static UserPaginationContainer getUsers(TenantIdentifier tenantIdentifier, + Storage storage, Integer limit, String timeJoinedOrder, @Nullable String paginationToken, @Nullable RECIPE_ID[] includeRecipeIds, @@ -701,13 +691,13 @@ public static UserPaginationContainer getUsers(TenantIdentifierWithStorage tenan throws StorageQueryException, UserPaginationToken.InvalidTokenException, TenantOrAppNotFoundException { AuthRecipeUserInfo[] users; if (paginationToken == null) { - users = tenantIdentifierWithStorage.getAuthRecipeStorage() - .getUsers(tenantIdentifierWithStorage, limit + 1, timeJoinedOrder, includeRecipeIds, null, + users = StorageUtils.getAuthRecipeStorage(storage) + .getUsers(tenantIdentifier, limit + 1, timeJoinedOrder, includeRecipeIds, null, null, dashboardSearchTags); } else { UserPaginationToken tokenInfo = UserPaginationToken.extractTokenInfo(paginationToken); - users = tenantIdentifierWithStorage.getAuthRecipeStorage() - .getUsers(tenantIdentifierWithStorage, limit + 1, timeJoinedOrder, includeRecipeIds, + users = StorageUtils.getAuthRecipeStorage(storage) + .getUsers(tenantIdentifier, limit + 1, timeJoinedOrder, includeRecipeIds, tokenInfo.userId, tokenInfo.timeJoined, dashboardSearchTags); } @@ -736,8 +726,7 @@ public static UserPaginationContainer getUsers(Main main, throws StorageQueryException, UserPaginationToken.InvalidTokenException { try { Storage storage = StorageLayer.getStorage(main); - return getUsers(new TenantIdentifierWithStorage( - null, null, null, storage), + return getUsers(TenantIdentifier.BASE_TENANT, storage, limit, timeJoinedOrder, paginationToken, includeRecipeIds, dashboardSearchTags); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); @@ -745,31 +734,32 @@ public static UserPaginationContainer getUsers(Main main, } @TestOnly - public static void deleteUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + public static void deleteUser(AppIdentifier appIdentifier, Storage storage, String userId, UserIdMapping userIdMapping) throws StorageQueryException, StorageTransactionLogicException { - deleteUser(appIdentifierWithStorage, userId, true, userIdMapping); + deleteUser(appIdentifier, storage, userId, true, userIdMapping); } - public static void deleteUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + public static void deleteUser(AppIdentifier appIdentifier, Storage storage, String userId, boolean removeAllLinkedAccounts, UserIdMapping userIdMapping) throws StorageQueryException, StorageTransactionLogicException { - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); - storage.startTransaction(con -> { - deleteUserHelper(con, appIdentifierWithStorage, userId, removeAllLinkedAccounts, userIdMapping); - storage.commitTransaction(con); + authRecipeStorage.startTransaction(con -> { + deleteUserHelper(con, appIdentifier, storage, userId, removeAllLinkedAccounts, userIdMapping); + authRecipeStorage.commitTransaction(con); return null; }); } - private static void deleteUserHelper(TransactionConnection con, AppIdentifierWithStorage appIdentifierWithStorage, + private static void deleteUserHelper(TransactionConnection con, AppIdentifier appIdentifier, + Storage storage, String userId, boolean removeAllLinkedAccounts, UserIdMapping userIdMapping) throws StorageQueryException { - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); String userIdToDeleteForNonAuthRecipeForRecipeUserId; String userIdToDeleteForAuthRecipe; @@ -799,8 +789,8 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit // in reference to // https://docs.google.com/spreadsheets/d/17hYV32B0aDCeLnSxbZhfRN2Y9b0LC2xUF44vV88RNAA/edit?usp=sharing // we want to check which state the db is in - if (((AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage()) - .doesUserIdExist_Transaction(con, appIdentifierWithStorage, userIdMapping.externalUserId)) { + if (authRecipeStorage + .doesUserIdExist_Transaction(con, appIdentifier, userIdMapping.externalUserId)) { // db is in state A4 // delete only from auth tables userIdToDeleteForAuthRecipe = userId; @@ -821,7 +811,7 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit // this user ID represents the non auth recipe stuff to delete for the primary user id String primaryUserIdToDeleteNonAuthRecipe = null; - AuthRecipeUserInfo userToDelete = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + AuthRecipeUserInfo userToDelete = authRecipeStorage.getPrimaryUserById_Transaction(appIdentifier, con, userIdToDeleteForAuthRecipe); if (userToDelete == null) { @@ -832,7 +822,7 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit if (userToDelete.getSupertokensUserId().equals(userIdToDeleteForAuthRecipe)) { primaryUserIdToDeleteNonAuthRecipe = userIdToDeleteForNonAuthRecipeForRecipeUserId; if (primaryUserIdToDeleteNonAuthRecipe == null) { - deleteAuthRecipeUser(con, appIdentifierWithStorage, userToDelete.getSupertokensUserId(), + deleteAuthRecipeUser(con, appIdentifier, storage, userToDelete.getSupertokensUserId(), true); return; } @@ -841,7 +831,8 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( con, - appIdentifierWithStorage, + appIdentifier, + storage, userToDelete.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (mappingResult != null) { primaryUserIdToDeleteNonAuthRecipe = mappingResult.externalUserId; @@ -859,19 +850,19 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit } if (!removeAllLinkedAccounts) { - deleteAuthRecipeUser(con, appIdentifierWithStorage, userIdToDeleteForAuthRecipe, + deleteAuthRecipeUser(con, appIdentifier, storage, userIdToDeleteForAuthRecipe, !userIdToDeleteForAuthRecipe.equals(userToDelete.getSupertokensUserId())); if (userIdToDeleteForNonAuthRecipeForRecipeUserId != null) { - deleteNonAuthRecipeUser(con, appIdentifierWithStorage, userIdToDeleteForNonAuthRecipeForRecipeUserId); + deleteNonAuthRecipeUser(con, appIdentifier, storage, userIdToDeleteForNonAuthRecipeForRecipeUserId); } if (primaryUserIdToDeleteNonAuthRecipe != null) { - deleteNonAuthRecipeUser(con, appIdentifierWithStorage, primaryUserIdToDeleteNonAuthRecipe); + deleteNonAuthRecipeUser(con, appIdentifier, storage, primaryUserIdToDeleteNonAuthRecipe); // this is only done to also delete the user ID mapping in case it exists, since we do not delete in the // previous call to deleteAuthRecipeUser above. - deleteAuthRecipeUser(con, appIdentifierWithStorage, userToDelete.getSupertokensUserId(), + deleteAuthRecipeUser(con, appIdentifier, storage, userToDelete.getSupertokensUserId(), true); } } else { @@ -880,9 +871,10 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit userIdToDeleteForAuthRecipe) ? userIdMapping : io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( con, - appIdentifierWithStorage, + appIdentifier, + storage, lM.getSupertokensUserId(), UserIdType.SUPERTOKENS); - deleteUserHelper(con, appIdentifierWithStorage, lM.getSupertokensUserId(), false, mappingResult); + deleteUserHelper(con, appIdentifier, storage, lM.getSupertokensUserId(), false, mappingResult); } } } @@ -891,66 +883,66 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit public static void deleteUser(Main main, String userId, boolean removeAllLinkedAccounts) throws StorageQueryException, StorageTransactionLogicException { Storage storage = StorageLayer.getStorage(main); - AppIdentifierWithStorage appIdentifier = new AppIdentifierWithStorage( - null, null, storage); + AppIdentifier appIdentifier = new AppIdentifier(null, null); UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(appIdentifier, - userId, UserIdType.ANY); + storage, userId, UserIdType.ANY); - deleteUser(appIdentifier, userId, removeAllLinkedAccounts, mapping); + deleteUser(appIdentifier, storage, userId, removeAllLinkedAccounts, mapping); } @TestOnly public static void deleteUser(Main main, String userId) throws StorageQueryException, StorageTransactionLogicException { Storage storage = StorageLayer.getStorage(main); - AppIdentifierWithStorage appIdentifier = new AppIdentifierWithStorage( - null, null, storage); + AppIdentifier appIdentifier = new AppIdentifier(null, null); UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(appIdentifier, - userId, UserIdType.ANY); + storage, userId, UserIdType.ANY); - deleteUser(appIdentifier, userId, mapping); + deleteUser(appIdentifier, storage, userId, mapping); } @TestOnly - public static void deleteUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static void deleteUser(AppIdentifier appIdentifier, Storage storage, String userId) throws StorageQueryException, StorageTransactionLogicException { - Storage storage = appIdentifierWithStorage.getStorage(); - UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(appIdentifierWithStorage, - userId, UserIdType.ANY); + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(appIdentifier, + storage, userId, UserIdType.ANY); - deleteUser(appIdentifierWithStorage, userId, mapping); + deleteUser(appIdentifier, storage, userId, mapping); } - private static void deleteNonAuthRecipeUser(TransactionConnection con, AppIdentifierWithStorage - appIdentifierWithStorage, String userId) + private static void deleteNonAuthRecipeUser(TransactionConnection con, AppIdentifier appIdentifier, + Storage storage, String userId) throws StorageQueryException { - appIdentifierWithStorage.getUserMetadataStorage() - .deleteUserMetadata_Transaction(con, appIdentifierWithStorage, userId); - ((SessionSQLStorage) appIdentifierWithStorage.getSessionStorage()) - .deleteSessionsOfUser_Transaction(con, appIdentifierWithStorage, userId); - appIdentifierWithStorage.getEmailVerificationStorage() - .deleteEmailVerificationUserInfo_Transaction(con, appIdentifierWithStorage, userId); - appIdentifierWithStorage.getUserRolesStorage() - .deleteAllRolesForUser_Transaction(con, appIdentifierWithStorage, userId); - appIdentifierWithStorage.getActiveUsersStorage() - .deleteUserActive_Transaction(con, appIdentifierWithStorage, userId); + StorageUtils.getUserMetadataStorage(storage) + .deleteUserMetadata_Transaction(con, appIdentifier, userId); + ((SessionSQLStorage) StorageUtils.getSessionStorage(storage)) + .deleteSessionsOfUser_Transaction(con, appIdentifier, userId); + StorageUtils.getEmailVerificationStorage(storage) + .deleteEmailVerificationUserInfo_Transaction(con, appIdentifier, userId); + StorageUtils.getUserRolesStorage(storage) + .deleteAllRolesForUser_Transaction(con, appIdentifier, userId); + // FIXME + // StorageUtils.getActiveUsersStorage(storage) + // .deleteUserActive_Transaction(con, appIdentifier, userId); + StorageUtils.getTOTPStorage(storage) + .removeUser_Transaction(con, appIdentifier, userId); } private static void deleteAuthRecipeUser(TransactionConnection con, - AppIdentifierWithStorage appIdentifierWithStorage, String - userId, boolean deleteFromUserIdToAppIdTableToo) + AppIdentifier appIdentifier, + Storage storage, + String userId, boolean deleteFromUserIdToAppIdTableToo) throws StorageQueryException { // auth recipe deletions here only - appIdentifierWithStorage.getEmailPasswordStorage() - .deleteEmailPasswordUser_Transaction(con, appIdentifierWithStorage, userId, deleteFromUserIdToAppIdTableToo); - appIdentifierWithStorage.getThirdPartyStorage() - .deleteThirdPartyUser_Transaction(con, appIdentifierWithStorage, userId, deleteFromUserIdToAppIdTableToo); - appIdentifierWithStorage.getPasswordlessStorage() - .deletePasswordlessUser_Transaction(con, appIdentifierWithStorage, userId, deleteFromUserIdToAppIdTableToo); + StorageUtils.getEmailPasswordStorage(storage) + .deleteEmailPasswordUser_Transaction(con, appIdentifier, userId, deleteFromUserIdToAppIdTableToo); + StorageUtils.getThirdPartyStorage(storage) + .deleteThirdPartyUser_Transaction(con, appIdentifier, userId, deleteFromUserIdToAppIdTableToo); + StorageUtils.getPasswordlessStorage(storage) + .deletePasswordlessUser_Transaction(con, appIdentifier, userId, deleteFromUserIdToAppIdTableToo); } - public static boolean deleteNonAuthRecipeUser(TenantIdentifierWithStorage - tenantIdentifierWithStorage, String userId) + public static boolean deleteNonAuthRecipeUser(TenantIdentifier tenantIdentifier, Storage storage, String userId) throws StorageQueryException { // UserMetadata is per app, so nothing to delete @@ -958,20 +950,20 @@ public static boolean deleteNonAuthRecipeUser(TenantIdentifierWithStorage boolean finalDidExist = false; boolean didExist = false; - didExist = tenantIdentifierWithStorage.getSessionStorage() - .deleteSessionsOfUser(tenantIdentifierWithStorage, userId); + didExist = StorageUtils.getSessionStorage(storage) + .deleteSessionsOfUser(tenantIdentifier, userId); finalDidExist = finalDidExist || didExist; - didExist = tenantIdentifierWithStorage.getEmailVerificationStorage() - .deleteEmailVerificationUserInfo(tenantIdentifierWithStorage, userId); + didExist = StorageUtils.getEmailVerificationStorage(storage) + .deleteEmailVerificationUserInfo(tenantIdentifier, userId); finalDidExist = finalDidExist || didExist; - didExist = (tenantIdentifierWithStorage.getUserRolesStorage() - .deleteAllRolesForUser(tenantIdentifierWithStorage, userId) > 0); + didExist = StorageUtils.getUserRolesStorage(storage) + .deleteAllRolesForUser(tenantIdentifier, userId) > 0; finalDidExist = finalDidExist || didExist; - didExist = tenantIdentifierWithStorage.getTOTPStorage() - .removeUser(tenantIdentifierWithStorage, userId); + didExist = StorageUtils.getTOTPStorage(storage) + .removeUser(tenantIdentifier, userId); finalDidExist = finalDidExist || didExist; finalDidExist = finalDidExist || didExist; diff --git a/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java b/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java index 215024858..cbb17c0c9 100644 --- a/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java +++ b/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java @@ -35,7 +35,6 @@ import io.supertokens.pluginInterface.dashboard.DashboardUser; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; @@ -102,19 +101,15 @@ protected void doTaskPerApp(AppIdentifier app) throws Exception { if (StorageLayer.getBaseStorage(main).getType() == STORAGE_TYPE.SQL) { { // Users count across all tenants Storage[] storages = StorageLayer.getStoragesForApp(main, app); - AppIdentifierWithStorage appIdentifierWithAllTenantStorages = new AppIdentifierWithStorage( - app.getConnectionUriDomain(), app.getAppId(), - StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main), storages - ); json.addProperty("usersCount", - AuthRecipe.getUsersCountAcrossAllTenants(appIdentifierWithAllTenantStorages, null)); + AuthRecipe.getUsersCountAcrossAllTenants(app, storages, null)); } { // Dashboard user emails // Dashboard APIs are app specific and are always stored on the public tenant DashboardUser[] dashboardUsers = Dashboard.getAllDashboardUsers( - app.withStorage(StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main)), main); + app, StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main), main); JsonArray dashboardUserEmails = new JsonArray(); for (DashboardUser user : dashboardUsers) { dashboardUserEmails.add(new JsonPrimitive(user.email)); diff --git a/src/main/java/io/supertokens/dashboard/Dashboard.java b/src/main/java/io/supertokens/dashboard/Dashboard.java index 746273aa5..ce9486891 100644 --- a/src/main/java/io/supertokens/dashboard/Dashboard.java +++ b/src/main/java/io/supertokens/dashboard/Dashboard.java @@ -23,6 +23,7 @@ import io.supertokens.featureflag.FeatureFlag; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.dashboard.DashboardSessionInfo; import io.supertokens.pluginInterface.dashboard.DashboardUser; import io.supertokens.pluginInterface.dashboard.exceptions.DuplicateEmailException; @@ -32,7 +33,6 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.Utils; @@ -55,26 +55,26 @@ public static DashboardUser signUpDashboardUser(Main main, String email, throws StorageQueryException, DuplicateEmailException, FeatureNotEnabledException { try { Storage storage = StorageLayer.getStorage(main); - return signUpDashboardUser(new AppIdentifierWithStorage(null, null, storage), + return signUpDashboardUser(new AppIdentifier(null, null), storage, main, email, password); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static DashboardUser signUpDashboardUser(AppIdentifierWithStorage appIdentifierWithStorage, Main main, String email, + public static DashboardUser signUpDashboardUser(AppIdentifier appIdentifier, Storage storage, Main main, String email, String password) throws StorageQueryException, DuplicateEmailException, FeatureNotEnabledException, TenantOrAppNotFoundException { - if (appIdentifierWithStorage.getDashboardStorage().getDashboardUserByEmail(appIdentifierWithStorage, email) != + if (StorageUtils.getDashboardStorage(storage).getDashboardUserByEmail(appIdentifier, email) != null) { throw new DuplicateEmailException(); } - if (!isDashboardFeatureFlagEnabled(main, appIdentifierWithStorage)) { - DashboardUser[] users = appIdentifierWithStorage.getDashboardStorage() - .getAllDashboardUsers(appIdentifierWithStorage); + if (!isDashboardFeatureFlagEnabled(main, appIdentifier)) { + DashboardUser[] users = StorageUtils.getDashboardStorage(storage) + .getAllDashboardUsers(appIdentifier); if (users.length >= MAX_NUMBER_OF_FREE_DASHBOARD_USERS) { throw new FeatureNotEnabledException( "Free user limit reached. Please subscribe to a SuperTokens core license key to allow more " + @@ -82,7 +82,7 @@ public static DashboardUser signUpDashboardUser(AppIdentifierWithStorage appIden } } - String hashedPassword = PasswordHashing.getInstance(main).createHashWithSalt(appIdentifierWithStorage, password); + String hashedPassword = PasswordHashing.getInstance(main).createHashWithSalt(appIdentifier, password); while (true) { String userId = Utils.getUUID(); @@ -90,7 +90,7 @@ public static DashboardUser signUpDashboardUser(AppIdentifierWithStorage appIden try { DashboardUser user = new DashboardUser(userId, email, hashedPassword, timeJoined); - appIdentifierWithStorage.getDashboardStorage().createNewDashboardUser(appIdentifierWithStorage, user); + StorageUtils.getDashboardStorage(storage).createNewDashboardUser(appIdentifier, user); return user; } catch (DuplicateUserIdException ignored) { // we retry with a new userId (while loop) @@ -102,15 +102,15 @@ public static DashboardUser signUpDashboardUser(AppIdentifierWithStorage appIden public static DashboardUser[] getAllDashboardUsers(Main main) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return getAllDashboardUsers(new AppIdentifierWithStorage(null, null, storage), main); + return getAllDashboardUsers(new AppIdentifier(null, null), storage, main); } - public static DashboardUser[] getAllDashboardUsers(AppIdentifierWithStorage appIdentifierWithStorage, Main main) + public static DashboardUser[] getAllDashboardUsers(AppIdentifier appIdentifier, Storage storage, Main main) throws StorageQueryException { - DashboardUser[] dashboardUsers = appIdentifierWithStorage.getDashboardStorage() - .getAllDashboardUsers(appIdentifierWithStorage); - if (isDashboardFeatureFlagEnabled(main, appIdentifierWithStorage)) { + DashboardUser[] dashboardUsers = StorageUtils.getDashboardStorage(storage) + .getAllDashboardUsers(appIdentifier); + if (isDashboardFeatureFlagEnabled(main, appIdentifier)) { return dashboardUsers; } else { List validDashboardUsers = new ArrayList<>(); @@ -126,26 +126,26 @@ public static String signInDashboardUser(Main main, String email, String passwor throws StorageQueryException, UserSuspendedException { try { Storage storage = StorageLayer.getStorage(main); - return signInDashboardUser(new AppIdentifierWithStorage(null, null, storage), + return signInDashboardUser(new AppIdentifier(null, null), storage, main, email, password); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static String signInDashboardUser(AppIdentifierWithStorage appIdentifierWithStorage, Main main, + public static String signInDashboardUser(AppIdentifier appIdentifier, Storage storage, Main main, String email, String password) throws StorageQueryException, UserSuspendedException, TenantOrAppNotFoundException { - DashboardUser user = appIdentifierWithStorage.getDashboardStorage() - .getDashboardUserByEmail(appIdentifierWithStorage, email); + DashboardUser user = StorageUtils.getDashboardStorage(storage) + .getDashboardUserByEmail(appIdentifier, email); if (user != null) { - if (isUserSuspended(appIdentifierWithStorage, main, email, null)) { + if (isUserSuspended(appIdentifier, storage, main, email, null)) { throw new UserSuspendedException(); } - if (PasswordHashing.getInstance(main).verifyPasswordWithHash(appIdentifierWithStorage, password, user.passwordHash)) { + if (PasswordHashing.getInstance(main).verifyPasswordWithHash(appIdentifier, password, user.passwordHash)) { // create a new session for the user try { - return createSessionForDashboardUser(appIdentifierWithStorage, user); + return createSessionForDashboardUser(appIdentifier, storage, user); } catch (UserIdNotFoundException e) { throw new IllegalStateException(e); } @@ -158,21 +158,21 @@ public static String signInDashboardUser(AppIdentifierWithStorage appIdentifierW public static boolean deleteUserWithUserId(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return deleteUserWithUserId(new AppIdentifierWithStorage(null, null, storage), userId); + return deleteUserWithUserId(new AppIdentifier(null, null), storage, userId); } - public static boolean deleteUserWithUserId(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static boolean deleteUserWithUserId(AppIdentifier appIdentifier, Storage storage, String userId) throws StorageQueryException { - return appIdentifierWithStorage.getDashboardStorage() - .deleteDashboardUserWithUserId(appIdentifierWithStorage, userId); + return StorageUtils.getDashboardStorage(storage) + .deleteDashboardUserWithUserId(appIdentifier, userId); } - private static boolean isUserSuspended(AppIdentifierWithStorage appIdentifierWithStorage, Main main, @Nullable String email, + private static boolean isUserSuspended(AppIdentifier appIdentifier, Storage storage, Main main, @Nullable String email, @Nullable String userId) throws StorageQueryException { - if (!isDashboardFeatureFlagEnabled(main, appIdentifierWithStorage)) { - DashboardUser[] users = appIdentifierWithStorage.getDashboardStorage() - .getAllDashboardUsers(appIdentifierWithStorage); + if (!isDashboardFeatureFlagEnabled(main, appIdentifier)) { + DashboardUser[] users = StorageUtils.getDashboardStorage(storage) + .getAllDashboardUsers(appIdentifier); if (email != null) { for (int i = 0; i < MAX_NUMBER_OF_FREE_DASHBOARD_USERS; i++) { @@ -199,15 +199,15 @@ private static boolean isUserSuspended(AppIdentifierWithStorage appIdentifierWit public static boolean deleteUserWithEmail(Main main, String email) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return deleteUserWithEmail(new AppIdentifierWithStorage(null, null, storage), email); + return deleteUserWithEmail(new AppIdentifier(null, null), storage, email); } - public static boolean deleteUserWithEmail(AppIdentifierWithStorage appIdentifierWithStorage, String email) + public static boolean deleteUserWithEmail(AppIdentifier appIdentifier, Storage storage, String email) throws StorageQueryException { - DashboardUser user = appIdentifierWithStorage.getDashboardStorage() - .getDashboardUserByEmail(appIdentifierWithStorage, email); + DashboardUser user = StorageUtils.getDashboardStorage(storage) + .getDashboardUserByEmail(appIdentifier, email); if (user != null) { - return deleteUserWithUserId(appIdentifierWithStorage, user.userId); + return deleteUserWithUserId(appIdentifier, storage, user.userId); } return false; } @@ -221,25 +221,25 @@ public static DashboardUser updateUsersCredentialsWithUserId(Main main, String u try { Storage storage = StorageLayer.getStorage(main); return updateUsersCredentialsWithUserId( - new AppIdentifierWithStorage(null, null, storage), main, userId, + new AppIdentifier(null, null), storage, main, userId, newEmail, newPassword); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static DashboardUser updateUsersCredentialsWithUserId(AppIdentifierWithStorage appIdentifierWithStorage, + public static DashboardUser updateUsersCredentialsWithUserId(AppIdentifier appIdentifier, Storage storage, Main main, String userId, String newEmail, String newPassword) throws StorageQueryException, DuplicateEmailException, UserIdNotFoundException, StorageTransactionLogicException, TenantOrAppNotFoundException { - DashboardSQLStorage storage = appIdentifierWithStorage.getDashboardStorage(); + DashboardSQLStorage dashboardStorage = StorageUtils.getDashboardStorage(storage); try { - storage.startTransaction(transaction -> { + dashboardStorage.startTransaction(transaction -> { if (newEmail != null) { try { - storage.updateDashboardUsersEmailWithUserId_Transaction(appIdentifierWithStorage, transaction, userId, + dashboardStorage.updateDashboardUsersEmailWithUserId_Transaction(appIdentifier, transaction, userId, newEmail); } catch (DuplicateEmailException | UserIdNotFoundException e) { throw new StorageTransactionLogicException(e); @@ -249,14 +249,14 @@ public static DashboardUser updateUsersCredentialsWithUserId(AppIdentifierWithSt if (newPassword != null) { try { String hashedPassword = PasswordHashing.getInstance(main) - .createHashWithSalt(appIdentifierWithStorage, newPassword); - storage.updateDashboardUsersPasswordWithUserId_Transaction(appIdentifierWithStorage, transaction, userId, + .createHashWithSalt(appIdentifier, newPassword); + dashboardStorage.updateDashboardUsersPasswordWithUserId_Transaction(appIdentifier, transaction, userId, hashedPassword); } catch (UserIdNotFoundException | TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(e); } } - storage.commitTransaction(transaction); + dashboardStorage.commitTransaction(transaction); return null; }); } catch (StorageTransactionLogicException e) { @@ -273,41 +273,41 @@ public static DashboardUser updateUsersCredentialsWithUserId(AppIdentifierWithSt } // revoke sessions for the user - DashboardSessionInfo[] sessionInfo = Dashboard.getAllDashboardSessionsForUser(appIdentifierWithStorage, userId); + DashboardSessionInfo[] sessionInfo = Dashboard.getAllDashboardSessionsForUser(appIdentifier, storage, userId); for (int i = 0; i < sessionInfo.length; i++) { - appIdentifierWithStorage.getDashboardStorage() - .revokeSessionWithSessionId(appIdentifierWithStorage, sessionInfo[i].sessionId); + StorageUtils.getDashboardStorage(storage) + .revokeSessionWithSessionId(appIdentifier, sessionInfo[i].sessionId); } - return appIdentifierWithStorage.getDashboardStorage() - .getDashboardUserByUserId(appIdentifierWithStorage, userId); + return StorageUtils.getDashboardStorage(storage) + .getDashboardUserByUserId(appIdentifier, userId); } @TestOnly public static DashboardUser getDashboardUserByEmail(Main main, String email) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return getDashboardUserByEmail(new AppIdentifierWithStorage(null, null, storage), email); + return getDashboardUserByEmail(new AppIdentifier(null, null), storage, email); } - public static DashboardUser getDashboardUserByEmail(AppIdentifierWithStorage appIdentifierWithStorage, String email) + public static DashboardUser getDashboardUserByEmail(AppIdentifier appIdentifier, Storage storage, String email) throws StorageQueryException { - return appIdentifierWithStorage.getDashboardStorage() - .getDashboardUserByEmail(appIdentifierWithStorage, email); + return StorageUtils.getDashboardStorage(storage) + .getDashboardUserByEmail(appIdentifier, email); } @TestOnly public static boolean revokeSessionWithSessionId(Main main, String sessionId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return revokeSessionWithSessionId(new AppIdentifierWithStorage(null, null, storage), sessionId); + return revokeSessionWithSessionId(new AppIdentifier(null, null), storage, sessionId); } - public static boolean revokeSessionWithSessionId(AppIdentifierWithStorage appIdentifierWithStorage, String sessionId) + public static boolean revokeSessionWithSessionId(AppIdentifier appIdentifier, Storage storage, String sessionId) throws StorageQueryException { - return appIdentifierWithStorage.getDashboardStorage() - .revokeSessionWithSessionId(appIdentifierWithStorage, sessionId); + return StorageUtils.getDashboardStorage(storage) + .revokeSessionWithSessionId(appIdentifier, sessionId); } @TestOnly @@ -316,14 +316,14 @@ public static DashboardSessionInfo[] getAllDashboardSessionsForUser(Main main, throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getAllDashboardSessionsForUser( - new AppIdentifierWithStorage(null, null, storage), userId); + new AppIdentifier(null, null), storage, userId); } - public static DashboardSessionInfo[] getAllDashboardSessionsForUser(AppIdentifierWithStorage appIdentifierWithStorage, + public static DashboardSessionInfo[] getAllDashboardSessionsForUser(AppIdentifier appIdentifier, Storage storage, String userId) throws StorageQueryException { - return appIdentifierWithStorage.getDashboardStorage() - .getAllSessionsForUserId(appIdentifierWithStorage, userId); + return StorageUtils.getDashboardStorage(storage) + .getAllSessionsForUserId(appIdentifier, userId); } private static boolean isDashboardFeatureFlagEnabled(Main main, AppIdentifier appIdentifier) { @@ -335,14 +335,14 @@ private static boolean isDashboardFeatureFlagEnabled(Main main, AppIdentifier ap } } - private static String createSessionForDashboardUser(AppIdentifierWithStorage appIdentifierWithStorage, + private static String createSessionForDashboardUser(AppIdentifier appIdentifier, Storage storage, DashboardUser user) throws StorageQueryException, UserIdNotFoundException { String sessionId = UUID.randomUUID().toString(); long timeCreated = System.currentTimeMillis(); long expiry = timeCreated + DASHBOARD_SESSION_DURATION; - appIdentifierWithStorage.getDashboardStorage() - .createNewDashboardUserSession(appIdentifierWithStorage, user.userId, sessionId, timeCreated, + StorageUtils.getDashboardStorage(storage) + .createNewDashboardUserSession(appIdentifier, user.userId, sessionId, timeCreated, expiry); return sessionId; } @@ -386,16 +386,16 @@ public static String validatePassword(String password) { public static boolean isValidUserSession(Main main, String sessionId) throws StorageQueryException, UserSuspendedException { Storage storage = StorageLayer.getStorage(main); - return isValidUserSession(new AppIdentifierWithStorage(null, null, storage), main, sessionId); + return isValidUserSession(new AppIdentifier(null, null), storage, main, sessionId); } - public static boolean isValidUserSession(AppIdentifierWithStorage appIdentifierWithStorage, Main main, String sessionId) + public static boolean isValidUserSession(AppIdentifier appIdentifier, Storage storage, Main main, String sessionId) throws StorageQueryException, UserSuspendedException { - DashboardSessionInfo sessionInfo = appIdentifierWithStorage.getDashboardStorage() - .getSessionInfoWithSessionId(appIdentifierWithStorage, sessionId); + DashboardSessionInfo sessionInfo = StorageUtils.getDashboardStorage(storage) + .getSessionInfoWithSessionId(appIdentifier, sessionId); if (sessionInfo != null) { // check if user is suspended - if (isUserSuspended(appIdentifierWithStorage, main, null, sessionInfo.userId)) { + if (isUserSuspended(appIdentifier, storage, main, null, sessionInfo.userId)) { throw new UserSuspendedException(); } return true; @@ -403,14 +403,14 @@ public static boolean isValidUserSession(AppIdentifierWithStorage appIdentifierW return false; } - public static String getEmailFromSessionId(AppIdentifierWithStorage appIdentifierWithStorage, Main main, String sessionId) throws StorageQueryException { - DashboardSessionInfo sessionInfo = appIdentifierWithStorage.getDashboardStorage() - .getSessionInfoWithSessionId(appIdentifierWithStorage, sessionId); + public static String getEmailFromSessionId(AppIdentifier appIdentifier, Storage storage, String sessionId) throws StorageQueryException { + DashboardSessionInfo sessionInfo = StorageUtils.getDashboardStorage(storage) + .getSessionInfoWithSessionId(appIdentifier, sessionId); if (sessionInfo != null) { String userId = sessionInfo.userId; - DashboardUser user = appIdentifierWithStorage.getDashboardStorage().getDashboardUserByUserId(appIdentifierWithStorage, userId); + DashboardUser user = StorageUtils.getDashboardStorage(storage).getDashboardUserByUserId(appIdentifier, userId); if (user != null) { return user.email; diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 97772a433..c92fae912 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -28,6 +28,7 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; @@ -37,12 +38,12 @@ import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; +import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantConfig; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.Utils; @@ -86,47 +87,45 @@ public static AuthRecipeUserInfo signUp(Main main, @Nonnull String email, @Nonnu throws DuplicateEmailException, StorageQueryException { try { Storage storage = StorageLayer.getStorage(main); - return signUp(new TenantIdentifierWithStorage(null, null, null, storage), + return signUp(new TenantIdentifier(null, null, null), storage, main, email, password); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } - public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, - @Nonnull String email, @Nonnull String password) + public static AuthRecipeUserInfo signUp(TenantIdentifier tenantIdentifier, Storage storage, Main main, + @Nonnull String email, @Nonnull String password) throws DuplicateEmailException, StorageQueryException, TenantOrAppNotFoundException, BadPermissionException { - TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); + TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifier); if (config == null) { - throw new TenantOrAppNotFoundException(tenantIdentifierWithStorage); + throw new TenantOrAppNotFoundException(tenantIdentifier); } if (!config.emailPasswordConfig.enabled) { throw new BadPermissionException("Email password login not enabled for tenant"); } String hashedPassword = PasswordHashing.getInstance(main) - .createHashWithSalt(tenantIdentifierWithStorage.toAppIdentifier(), password); + .createHashWithSalt(tenantIdentifier.toAppIdentifier(), password); while (true) { String userId = Utils.getUUID(); long timeJoined = System.currentTimeMillis(); try { - AuthRecipeUserInfo newUser = tenantIdentifierWithStorage.getEmailPasswordStorage() - .signUp(tenantIdentifierWithStorage, userId, email, hashedPassword, timeJoined); + AuthRecipeUserInfo newUser = StorageUtils.getEmailPasswordStorage(storage) + .signUp(tenantIdentifier, userId, email, hashedPassword, timeJoined); if (Utils.isFakeEmail(email)) { try { - tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { + EmailVerificationSQLStorage evStorage = StorageUtils.getEmailVerificationStorage(storage); + evStorage.startTransaction(con -> { try { - - tenantIdentifierWithStorage.getEmailVerificationStorage() - .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + evStorage.updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con, newUser.getSupertokensUserId(), email, true); - tenantIdentifierWithStorage.getEmailVerificationStorage() - .commitTransaction(con); + evStorage.commitTransaction(con); return null; } catch (TenantOrAppNotFoundException e) { @@ -158,51 +157,51 @@ public static ImportUserResponse importUserWithPasswordHash(Main main, @Nonnull Storage storage = StorageLayer.getStorage(main); return importUserWithPasswordHash( - new TenantIdentifierWithStorage(null, null, null, storage), main, email, + new TenantIdentifier(null, null, null), storage, main, email, passwordHash, hashingAlgorithm); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } - public static ImportUserResponse importUserWithPasswordHash(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static ImportUserResponse importUserWithPasswordHash(TenantIdentifier tenantIdentifier, Storage storage, Main main, @Nonnull String email, @Nonnull String passwordHash, @Nullable CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm) throws StorageQueryException, StorageTransactionLogicException, UnsupportedPasswordHashingFormatException, TenantOrAppNotFoundException, BadPermissionException { - TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); + TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifier); if (config == null) { - throw new TenantOrAppNotFoundException(tenantIdentifierWithStorage); + throw new TenantOrAppNotFoundException(tenantIdentifier); } if (!config.emailPasswordConfig.enabled) { throw new BadPermissionException("Email password login not enabled for tenant"); } PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat( - tenantIdentifierWithStorage.toAppIdentifier(), main, + tenantIdentifier.toAppIdentifier(), main, passwordHash, hashingAlgorithm); while (true) { String userId = Utils.getUUID(); long timeJoined = System.currentTimeMillis(); - EmailPasswordSQLStorage storage = tenantIdentifierWithStorage.getEmailPasswordStorage(); + EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage); try { - AuthRecipeUserInfo userInfo = storage.signUp(tenantIdentifierWithStorage, userId, email, passwordHash, + AuthRecipeUserInfo userInfo = epStorage.signUp(tenantIdentifier, userId, email, passwordHash, timeJoined); return new ImportUserResponse(false, userInfo); } catch (DuplicateUserIdException e) { // we retry with a new userId } catch (DuplicateEmailException e) { - AuthRecipeUserInfo[] allUsers = storage.listPrimaryUsersByEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] allUsers = epStorage.listPrimaryUsersByEmail(tenantIdentifier, email); AuthRecipeUserInfo userInfoToBeUpdated = null; LoginMethod loginMethod = null; for (AuthRecipeUserInfo currUser : allUsers) { for (LoginMethod currLM : currUser.loginMethods) { - if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.tenantIds.contains(tenantIdentifier.getTenantId())) { userInfoToBeUpdated = currUser; loginMethod = currLM; break; @@ -212,8 +211,8 @@ public static ImportUserResponse importUserWithPasswordHash(TenantIdentifierWith if (userInfoToBeUpdated != null) { LoginMethod finalLoginMethod = loginMethod; - storage.startTransaction(con -> { - storage.updateUsersPassword_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + epStorage.startTransaction(con -> { + epStorage.updateUsersPassword_Transaction(tenantIdentifier.toAppIdentifier(), con, finalLoginMethod.getSupertokensUserId(), passwordHash); return null; }); @@ -230,7 +229,7 @@ public static ImportUserResponse importUserWithPasswordHash(Main main, @Nonnull try { Storage storage = StorageLayer.getStorage(main); return importUserWithPasswordHash( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, main, email, passwordHash, null); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); @@ -243,35 +242,35 @@ public static AuthRecipeUserInfo signIn(Main main, @Nonnull String email, throws StorageQueryException, WrongCredentialsException { try { Storage storage = StorageLayer.getStorage(main); - return signIn(new TenantIdentifierWithStorage(null, null, null, storage), + return signIn(new TenantIdentifier(null, null, null), storage, main, email, password); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } - public static AuthRecipeUserInfo signIn(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static AuthRecipeUserInfo signIn(TenantIdentifier tenantIdentifier, Storage storage, Main main, @Nonnull String email, @Nonnull String password) throws StorageQueryException, WrongCredentialsException, TenantOrAppNotFoundException, BadPermissionException { - TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); + TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifier); if (config == null) { - throw new TenantOrAppNotFoundException(tenantIdentifierWithStorage); + throw new TenantOrAppNotFoundException(tenantIdentifier); } if (!config.emailPasswordConfig.enabled) { throw new BadPermissionException("Email password login not enabled for tenant"); } - AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getAuthRecipeStorage() - .listPrimaryUsersByEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] users = StorageUtils.getEmailPasswordStorage(storage) + .listPrimaryUsersByEmail(tenantIdentifier, email); AuthRecipeUserInfo user = null; LoginMethod lM = null; for (AuthRecipeUserInfo currUser : users) { for (LoginMethod currLM : currUser.loginMethods) { - if (currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.email.equals(email) && currLM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + if (currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.email.equals(email) && currLM.tenantIds.contains(tenantIdentifier.getTenantId())) { user = currUser; lM = currLM; } @@ -284,7 +283,7 @@ public static AuthRecipeUserInfo signIn(TenantIdentifierWithStorage tenantIdenti try { if (!PasswordHashing.getInstance(main) - .verifyPasswordWithHash(tenantIdentifierWithStorage.toAppIdentifier(), password, + .verifyPasswordWithHash(tenantIdentifier.toAppIdentifier(), password, lM.passwordHash)) { throw new WrongCredentialsException(); } @@ -309,7 +308,7 @@ public static String generatePasswordResetTokenBeforeCdi4_0(Main main, String us try { Storage storage = StorageLayer.getStorage(main); return generatePasswordResetTokenBeforeCdi4_0( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, main, userId); } catch (TenantOrAppNotFoundException | BadPermissionException | WebserverAPI.BadRequestException e) { throw new IllegalStateException(e); @@ -322,7 +321,7 @@ public static String generatePasswordResetTokenBeforeCdi4_0WithoutAddingEmail(Ma try { Storage storage = StorageLayer.getStorage(main); return generatePasswordResetToken( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, main, userId, null); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); @@ -335,21 +334,19 @@ public static String generatePasswordResetToken(Main main, String userId, String try { Storage storage = StorageLayer.getStorage(main); return generatePasswordResetToken( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, main, userId, email); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } - public static String generatePasswordResetTokenBeforeCdi4_0(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static String generatePasswordResetTokenBeforeCdi4_0(TenantIdentifier tenantIdentifier, Storage storage, Main main, String userId) throws InvalidKeySpecException, NoSuchAlgorithmException, StorageQueryException, UnknownUserIdException, TenantOrAppNotFoundException, BadPermissionException, WebserverAPI.BadRequestException { - AppIdentifierWithStorage appIdentifierWithStorage = - tenantIdentifierWithStorage.toAppIdentifierWithStorage(); - AuthRecipeUserInfo user = AuthRecipe.getUserById(appIdentifierWithStorage, userId); + AuthRecipeUserInfo user = AuthRecipe.getUserById(tenantIdentifier.toAppIdentifier(), storage, userId); if (user == null) { throw new UnknownUserIdException(); } @@ -361,17 +358,17 @@ public static String generatePasswordResetTokenBeforeCdi4_0(TenantIdentifierWith // this used to be the behaviour of the older CDI version and it was enforced via a fkey constraint throw new UnknownUserIdException(); } - return generatePasswordResetToken(tenantIdentifierWithStorage, main, userId, user.loginMethods[0].email); + return generatePasswordResetToken(tenantIdentifier, storage, main, userId, user.loginMethods[0].email); } - public static String generatePasswordResetToken(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static String generatePasswordResetToken(TenantIdentifier tenantIdentifier, Storage storage, Main main, String userId, String email) throws InvalidKeySpecException, NoSuchAlgorithmException, StorageQueryException, UnknownUserIdException, TenantOrAppNotFoundException, BadPermissionException { - TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); + TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifier); if (config == null) { - throw new TenantOrAppNotFoundException(tenantIdentifierWithStorage); + throw new TenantOrAppNotFoundException(tenantIdentifier); } if (!config.emailPasswordConfig.enabled) { throw new BadPermissionException("Email password login not enabled for tenant"); @@ -399,10 +396,10 @@ public static String generatePasswordResetToken(TenantIdentifierWithStorage tena String hashedToken = Utils.hashSHA256(token); try { - tenantIdentifierWithStorage.getEmailPasswordStorage().addPasswordResetToken( - tenantIdentifierWithStorage.toAppIdentifier(), new PasswordResetTokenInfo(userId, + StorageUtils.getEmailPasswordStorage(storage).addPasswordResetToken( + tenantIdentifier.toAppIdentifier(), new PasswordResetTokenInfo(userId, hashedToken, System.currentTimeMillis() + - getPasswordResetTokenLifetime(tenantIdentifierWithStorage, main), email)); + getPasswordResetTokenLifetime(tenantIdentifier, main), email)); return token; } catch (DuplicatePasswordResetTokenException ignored) { } @@ -417,7 +414,7 @@ public static String resetPassword(Main main, String token, StorageTransactionLogicException { try { Storage storage = StorageLayer.getStorage(main); - return resetPassword(new TenantIdentifierWithStorage(null, null, null, storage), + return resetPassword(new TenantIdentifier(null, null, null), storage, main, token, password); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); @@ -425,17 +422,17 @@ public static String resetPassword(Main main, String token, } @Deprecated - public static String resetPassword(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String token, + public static String resetPassword(TenantIdentifier tenantIdentifier, Storage storage, Main main, String token, String password) throws ResetPasswordInvalidTokenException, NoSuchAlgorithmException, StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException { String hashedToken = Utils.hashSHA256(token); String hashedPassword = PasswordHashing.getInstance(main) - .createHashWithSalt(tenantIdentifierWithStorage.toAppIdentifier(), password); - EmailPasswordSQLStorage storage = tenantIdentifierWithStorage.getEmailPasswordStorage(); + .createHashWithSalt(tenantIdentifier.toAppIdentifier(), password); + EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage); - PasswordResetTokenInfo resetInfo = storage.getPasswordResetTokenInfo( - tenantIdentifierWithStorage.toAppIdentifier(), hashedToken); + PasswordResetTokenInfo resetInfo = epStorage.getPasswordResetTokenInfo( + tenantIdentifier.toAppIdentifier(), hashedToken); if (resetInfo == null) { throw new ResetPasswordInvalidTokenException(); @@ -444,10 +441,10 @@ public static String resetPassword(TenantIdentifierWithStorage tenantIdentifierW final String userId = resetInfo.userId; try { - return storage.startTransaction(con -> { + return epStorage.startTransaction(con -> { - PasswordResetTokenInfo[] allTokens = storage.getAllPasswordResetTokenInfoForUser_Transaction( - tenantIdentifierWithStorage.toAppIdentifier(), con, + PasswordResetTokenInfo[] allTokens = epStorage.getAllPasswordResetTokenInfoForUser_Transaction( + tenantIdentifier.toAppIdentifier(), con, userId); PasswordResetTokenInfo matchedToken = null; @@ -462,19 +459,19 @@ public static String resetPassword(TenantIdentifierWithStorage tenantIdentifierW throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); } - storage.deleteAllPasswordResetTokensForUser_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), + epStorage.deleteAllPasswordResetTokensForUser_Transaction(tenantIdentifier.toAppIdentifier(), con, userId); if (matchedToken.tokenExpiry < System.currentTimeMillis()) { - storage.commitTransaction(con); + epStorage.commitTransaction(con); throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); } - storage.updateUsersPassword_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, userId, + epStorage.updateUsersPassword_Transaction(tenantIdentifier.toAppIdentifier(), con, userId, hashedPassword); - storage.commitTransaction(con); + epStorage.commitTransaction(con); return userId; }); } catch (StorageTransactionLogicException e) { @@ -491,7 +488,7 @@ public static ConsumeResetPasswordTokenResult consumeResetPasswordToken(Main mai StorageTransactionLogicException { try { Storage storage = StorageLayer.getStorage(main); - return consumeResetPasswordToken(new TenantIdentifierWithStorage(null, null, null, storage), + return consumeResetPasswordToken(new TenantIdentifier(null, null, null), storage, token); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); @@ -509,15 +506,15 @@ public ConsumeResetPasswordTokenResult(String userId, String email) { } public static ConsumeResetPasswordTokenResult consumeResetPasswordToken( - TenantIdentifierWithStorage tenantIdentifierWithStorage, String token) + TenantIdentifier tenantIdentifier, Storage storage, String token) throws ResetPasswordInvalidTokenException, NoSuchAlgorithmException, StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException { String hashedToken = Utils.hashSHA256(token); - EmailPasswordSQLStorage storage = tenantIdentifierWithStorage.getEmailPasswordStorage(); + EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage); - PasswordResetTokenInfo resetInfo = storage.getPasswordResetTokenInfo( - tenantIdentifierWithStorage.toAppIdentifier(), hashedToken); + PasswordResetTokenInfo resetInfo = epStorage.getPasswordResetTokenInfo( + tenantIdentifier.toAppIdentifier(), hashedToken); if (resetInfo == null) { throw new ResetPasswordInvalidTokenException(); @@ -526,10 +523,10 @@ public static ConsumeResetPasswordTokenResult consumeResetPasswordToken( final String userId = resetInfo.userId; try { - return storage.startTransaction(con -> { + return epStorage.startTransaction(con -> { - PasswordResetTokenInfo[] allTokens = storage.getAllPasswordResetTokenInfoForUser_Transaction( - tenantIdentifierWithStorage.toAppIdentifier(), con, + PasswordResetTokenInfo[] allTokens = epStorage.getAllPasswordResetTokenInfoForUser_Transaction( + tenantIdentifier.toAppIdentifier(), con, userId); PasswordResetTokenInfo matchedToken = null; @@ -544,22 +541,21 @@ public static ConsumeResetPasswordTokenResult consumeResetPasswordToken( throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); } - storage.deleteAllPasswordResetTokensForUser_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), + epStorage.deleteAllPasswordResetTokensForUser_Transaction(tenantIdentifier.toAppIdentifier(), con, userId); if (matchedToken.tokenExpiry < System.currentTimeMillis()) { - storage.commitTransaction(con); + epStorage.commitTransaction(con); throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); } - storage.commitTransaction(con); + epStorage.commitTransaction(con); if (matchedToken.email == null) { // this is possible if the token was generated before migration, and then consumed // after migration - AppIdentifierWithStorage appIdentifierWithStorage = - tenantIdentifierWithStorage.toAppIdentifierWithStorage(); - AuthRecipeUserInfo user = AuthRecipe.getUserById(appIdentifierWithStorage, userId); + AuthRecipeUserInfo user = AuthRecipe.getUserById(tenantIdentifier.toAppIdentifier(), storage, + userId); if (user == null) { throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); } @@ -590,25 +586,25 @@ public static void updateUsersEmailOrPassword(Main main, UnknownUserIdException, DuplicateEmailException, EmailChangeNotAllowedException { try { Storage storage = StorageLayer.getStorage(main); - updateUsersEmailOrPassword(new AppIdentifierWithStorage(null, null, storage), + updateUsersEmailOrPassword(new AppIdentifier(null, null), storage, main, userId, email, password); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdentifierWithStorage, Main main, + public static void updateUsersEmailOrPassword(AppIdentifier appIdentifier, Storage storage, Main main, @Nonnull String userId, @Nullable String email, @Nullable String password) throws StorageQueryException, StorageTransactionLogicException, UnknownUserIdException, DuplicateEmailException, TenantOrAppNotFoundException, EmailChangeNotAllowedException { - EmailPasswordSQLStorage storage = appIdentifierWithStorage.getEmailPasswordStorage(); - AuthRecipeSQLStorage authRecipeStorage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); try { - storage.startTransaction(transaction -> { + epStorage.startTransaction(transaction -> { try { - AuthRecipeUserInfo user = authRecipeStorage.getPrimaryUserById_Transaction(appIdentifierWithStorage, + AuthRecipeUserInfo user = authRecipeStorage.getPrimaryUserById_Transaction(appIdentifier, transaction, userId); if (user == null) { @@ -630,7 +626,7 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti for (String tenantId : user.tenantIds) { AuthRecipeUserInfo[] existingUsersWithNewEmail = authRecipeStorage.listPrimaryUsersByEmail_Transaction( - appIdentifierWithStorage, transaction, + appIdentifier, transaction, email); for (AuthRecipeUserInfo userWithSameEmail : existingUsersWithNewEmail) { @@ -646,7 +642,7 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti } try { - storage.updateUsersEmail_Transaction(appIdentifierWithStorage, transaction, + epStorage.updateUsersEmail_Transaction(appIdentifier, transaction, userId, email); } catch (DuplicateEmailException e) { throw new StorageTransactionLogicException(e); @@ -655,12 +651,12 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti if (password != null) { String hashedPassword = PasswordHashing.getInstance(main) - .createHashWithSalt(appIdentifierWithStorage, password); - storage.updateUsersPassword_Transaction(appIdentifierWithStorage, transaction, userId, + .createHashWithSalt(appIdentifier, password); + epStorage.updateUsersPassword_Transaction(appIdentifier, transaction, userId, hashedPassword); } - storage.commitTransaction(transaction); + epStorage.commitTransaction(transaction); return null; } catch (TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(e); @@ -686,17 +682,17 @@ public static AuthRecipeUserInfo getUserUsingId(Main main, String userId) throws StorageQueryException { try { Storage storage = StorageLayer.getStorage(main); - return getUserUsingId(new AppIdentifierWithStorage(null, null, storage), userId); + return getUserUsingId(new AppIdentifier(null, null), storage, userId); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } @Deprecated - public static AuthRecipeUserInfo getUserUsingId(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static AuthRecipeUserInfo getUserUsingId(AppIdentifier appIdentifier, Storage storage, String userId) throws StorageQueryException, TenantOrAppNotFoundException { - AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() - .getPrimaryUserById(appIdentifierWithStorage, userId); + AuthRecipeUserInfo result = StorageUtils.getAuthRecipeStorage(storage) + .getPrimaryUserById(appIdentifier, userId); if (result == null) { return null; } @@ -709,11 +705,11 @@ public static AuthRecipeUserInfo getUserUsingId(AppIdentifierWithStorage appIden } @Deprecated - public static AuthRecipeUserInfo getUserUsingEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static AuthRecipeUserInfo getUserUsingEmail(TenantIdentifier tenantIdentifier, Storage storage, String email) throws StorageQueryException, TenantOrAppNotFoundException { - AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getAuthRecipeStorage().listPrimaryUsersByEmail( - tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] users = StorageUtils.getEmailPasswordStorage(storage).listPrimaryUsersByEmail( + tenantIdentifier, email); // filter used based on login method for (AuthRecipeUserInfo user : users) { for (LoginMethod lM : user.loginMethods) { diff --git a/src/main/java/io/supertokens/emailverification/EmailVerification.java b/src/main/java/io/supertokens/emailverification/EmailVerification.java index 14bcca800..9999599f4 100644 --- a/src/main/java/io/supertokens/emailverification/EmailVerification.java +++ b/src/main/java/io/supertokens/emailverification/EmailVerification.java @@ -21,14 +21,14 @@ import io.supertokens.emailverification.exception.EmailAlreadyVerifiedException; import io.supertokens.emailverification.exception.EmailVerificationInvalidTokenException; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.emailverification.EmailVerificationTokenInfo; import io.supertokens.pluginInterface.emailverification.exception.DuplicateEmailVerificationTokenException; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.Utils; @@ -43,9 +43,8 @@ public class EmailVerification { @TestOnly public static long getEmailVerificationTokenLifetimeForTests(Main main) { try { - Storage storage = StorageLayer.getStorage(main); return getEmailVerificationTokenLifetime( - new TenantIdentifierWithStorage(null, null, null, storage), main); + new TenantIdentifier(null, null, null), main); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } @@ -63,20 +62,20 @@ public static String generateEmailVerificationToken(Main main, String userId, St try { Storage storage = StorageLayer.getStorage(main); return generateEmailVerificationToken( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, main, userId, email); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static String generateEmailVerificationToken(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static String generateEmailVerificationToken(TenantIdentifier tenantIdentifier, Storage storage, Main main, String userId, String email) throws InvalidKeySpecException, NoSuchAlgorithmException, StorageQueryException, EmailAlreadyVerifiedException, TenantOrAppNotFoundException { - if (tenantIdentifierWithStorage.getEmailVerificationStorage() - .isEmailVerified(tenantIdentifierWithStorage.toAppIdentifier(), userId, email)) { + if (StorageUtils.getEmailVerificationStorage(storage) + .isEmailVerified(tenantIdentifier.toAppIdentifier(), userId, email)) { throw new EmailAlreadyVerifiedException(); } @@ -102,11 +101,11 @@ public static String generateEmailVerificationToken(TenantIdentifierWithStorage String hashedToken = getHashedToken(token); try { - tenantIdentifierWithStorage.getEmailVerificationStorage() - .addEmailVerificationToken(tenantIdentifierWithStorage, + StorageUtils.getEmailVerificationStorage(storage) + .addEmailVerificationToken(tenantIdentifier, new EmailVerificationTokenInfo(userId, hashedToken, System.currentTimeMillis() + - getEmailVerificationTokenLifetime(tenantIdentifierWithStorage, main), email)); + getEmailVerificationTokenLifetime(tenantIdentifier, main), email)); return token; } catch (DuplicateEmailVerificationTokenException ignored) { } @@ -119,23 +118,23 @@ public static User verifyEmail(Main main, String token) EmailVerificationInvalidTokenException, NoSuchAlgorithmException, StorageTransactionLogicException { try { Storage storage = StorageLayer.getStorage(main); - return verifyEmail(new TenantIdentifierWithStorage(null, null, null, storage), token); + return verifyEmail(new TenantIdentifier(null, null, null), storage, token); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static User verifyEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, String token) + public static User verifyEmail(TenantIdentifier tenantIdentifier, Storage storage, String token) throws StorageQueryException, EmailVerificationInvalidTokenException, NoSuchAlgorithmException, StorageTransactionLogicException, TenantOrAppNotFoundException { String hashedToken = getHashedToken(token); - EmailVerificationSQLStorage storage = tenantIdentifierWithStorage.getEmailVerificationStorage(); + EmailVerificationSQLStorage evStorage = StorageUtils.getEmailVerificationStorage(storage); - final EmailVerificationTokenInfo tokenInfo = storage.getEmailVerificationTokenInfo( - tenantIdentifierWithStorage, hashedToken); + final EmailVerificationTokenInfo tokenInfo = evStorage.getEmailVerificationTokenInfo( + tenantIdentifier, hashedToken); if (tokenInfo == null) { throw new EmailVerificationInvalidTokenException(); } @@ -143,10 +142,10 @@ public static User verifyEmail(TenantIdentifierWithStorage tenantIdentifierWithS final String userId = tokenInfo.userId; try { - return storage.startTransaction(con -> { + return evStorage.startTransaction(con -> { - EmailVerificationTokenInfo[] allTokens = storage - .getAllEmailVerificationTokenInfoForUser_Transaction(tenantIdentifierWithStorage, con, + EmailVerificationTokenInfo[] allTokens = evStorage + .getAllEmailVerificationTokenInfoForUser_Transaction(tenantIdentifier, con, userId, tokenInfo.email); EmailVerificationTokenInfo matchedToken = null; @@ -161,22 +160,22 @@ public static User verifyEmail(TenantIdentifierWithStorage tenantIdentifierWithS throw new StorageTransactionLogicException(new EmailVerificationInvalidTokenException()); } - storage.deleteAllEmailVerificationTokensForUser_Transaction(tenantIdentifierWithStorage, con, + evStorage.deleteAllEmailVerificationTokensForUser_Transaction(tenantIdentifier, con, userId, tokenInfo.email); if (matchedToken.tokenExpiry < System.currentTimeMillis()) { - storage.commitTransaction(con); + evStorage.commitTransaction(con); throw new StorageTransactionLogicException(new EmailVerificationInvalidTokenException()); } try { - storage.updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, userId, + evStorage.updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con, userId, tokenInfo.email, true); } catch (TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(e); } - storage.commitTransaction(con); + evStorage.commitTransaction(con); return new User(userId, tokenInfo.email); }); @@ -194,28 +193,28 @@ public static User verifyEmail(TenantIdentifierWithStorage tenantIdentifierWithS public static boolean isEmailVerified(Main main, String userId, String email) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return isEmailVerified(new AppIdentifierWithStorage(null, null, storage), + return isEmailVerified(new AppIdentifier(null, null), storage, userId, email); } - public static boolean isEmailVerified(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + public static boolean isEmailVerified(AppIdentifier appIdentifier, Storage storage, String userId, String email) throws StorageQueryException { - return appIdentifierWithStorage.getEmailVerificationStorage() - .isEmailVerified(appIdentifierWithStorage, userId, email); + return StorageUtils.getEmailVerificationStorage(storage) + .isEmailVerified(appIdentifier, userId, email); } @TestOnly public static void revokeAllTokens(Main main, String userId, String email) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - revokeAllTokens(new TenantIdentifierWithStorage(null, null, null, storage), + revokeAllTokens(new TenantIdentifier(null, null, null), storage, userId, email); } - public static void revokeAllTokens(TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId, + public static void revokeAllTokens(TenantIdentifier tenantIdentifier, Storage storage, String userId, String email) throws StorageQueryException { - tenantIdentifierWithStorage.getEmailVerificationStorage() - .revokeAllTokens(tenantIdentifierWithStorage, userId, email); + StorageUtils.getEmailVerificationStorage(storage) + .revokeAllTokens(tenantIdentifier, userId, email); } @TestOnly @@ -223,17 +222,16 @@ public static void unverifyEmail(Main main, String userId, String email) throws StorageQueryException { try { Storage storage = StorageLayer.getStorage(main); - unverifyEmail(new AppIdentifierWithStorage(null, null, storage), - userId, email); + unverifyEmail(new AppIdentifier(null, null), storage, userId, email); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static void unverifyEmail(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + public static void unverifyEmail(AppIdentifier appIdentifier, Storage storage, String userId, String email) throws StorageQueryException, TenantOrAppNotFoundException { - appIdentifierWithStorage.getEmailVerificationStorage() - .unverifyEmail(appIdentifierWithStorage, userId, email); + StorageUtils.getEmailVerificationStorage(storage) + .unverifyEmail(appIdentifier, userId, email); } private static String getHashedToken(String token) throws NoSuchAlgorithmException { diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index c237811c5..a395238eb 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -1843,7 +1843,7 @@ public int deleteUserMetadata(AppIdentifier appIdentifier, String userId) throws @Override public void addRoleToUser(TenantIdentifier tenantIdentifier, String userId, String role) - throws StorageQueryException, UnknownRoleException, DuplicateUserRoleMappingException, + throws StorageQueryException, DuplicateUserRoleMappingException, TenantOrAppNotFoundException { try { UserRolesQueries.addRoleToUser(this, tenantIdentifier, userId, role); @@ -1852,13 +1852,6 @@ public void addRoleToUser(TenantIdentifier tenantIdentifier, String userId, Stri SQLiteConfig config = Config.getConfig(this); String serverErrorMessage = e.getMessage(); - if (isForeignKeyConstraintError( - serverErrorMessage, - config.getRolesTable(), - new String[]{"app_id", "role"}, - new Object[]{tenantIdentifier.getAppId(), role})) { - throw new UnknownRoleException(); - } if (isPrimaryKeyError(serverErrorMessage, config.getUserRolesTable(), new String[]{"app_id", "tenant_id", "user_id", "role"})) { throw new DuplicateUserRoleMappingException(); @@ -1934,6 +1927,16 @@ public boolean deleteRole(AppIdentifier appIdentifier, String role) throws Stora } } + @Override + public boolean deleteAllUserRoleAssociationsForRole(AppIdentifier appIdentifier, String role) + throws StorageQueryException { + try { + return UserRolesQueries.deleteAllUserRoleAssociationsForRole(this, appIdentifier, role); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + @Override public String[] getRoles(AppIdentifier appIdentifier) throws StorageQueryException { try { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/UserRolesQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/UserRolesQueries.java index 721bcae06..5c954ad46 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/UserRolesQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/UserRolesQueries.java @@ -74,8 +74,6 @@ public static String getQueryToCreateUserRolesTable(Start start) { + "user_id VARCHAR(128) NOT NULL," + "role VARCHAR(255) NOT NULL," + "PRIMARY KEY(app_id, tenant_id, user_id, role)," - + "FOREIGN KEY(app_id, role) REFERENCES " + Config.getConfig(start).getRolesTable() - + " (app_id, role) ON DELETE CASCADE," + "FOREIGN KEY(app_id, tenant_id) REFERENCES " + Config.getConfig(start).getTenantsTable() + " (app_id, tenant_id) ON DELETE CASCADE" + ");"; @@ -113,8 +111,9 @@ public static void addPermissionToRoleOrDoNothingIfExists_Transaction(Start star }); } - public static boolean deleteRole(Start start, AppIdentifier appIdentifier, String role) throws SQLException, StorageQueryException { - + public static boolean deleteRole(Start start, AppIdentifier appIdentifier, + String role) throws SQLException, StorageQueryException { + try { return start.startTransaction(con -> { // Row lock must be taken to delete the role, otherwise the table may be locked for delete @@ -341,4 +340,14 @@ public static int deleteAllRolesForUser_Transaction(Connection con, Start start, pst.setString(2, userId); }); } + + public static boolean deleteAllUserRoleAssociationsForRole(Start start, AppIdentifier appIdentifier, String role) + throws SQLException, StorageQueryException { + String QUERY = "DELETE FROM " + getConfig(start).getUserRolesTable() + + " WHERE app_id = ? AND role = ? ;"; + return update(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, role); + }) >= 1; + } } diff --git a/src/main/java/io/supertokens/multitenancy/Multitenancy.java b/src/main/java/io/supertokens/multitenancy/Multitenancy.java index 2cb068855..2342599dc 100644 --- a/src/main/java/io/supertokens/multitenancy/Multitenancy.java +++ b/src/main/java/io/supertokens/multitenancy/Multitenancy.java @@ -28,6 +28,8 @@ import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.exception.*; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; @@ -432,7 +434,7 @@ public static boolean deleteConnectionUriDomain(String connectionUriDomain, Main return didExist; } - public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static boolean addUserIdToTenant(Main main, TenantIdentifier tenantIdentifier, Storage storage, String userId) throws TenantOrAppNotFoundException, UnknownUserIdException, StorageQueryException, FeatureNotEnabledException, DuplicateEmailException, DuplicatePhoneNumberException, @@ -444,11 +446,11 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t throw new FeatureNotEnabledException(EE_FEATURES.MULTI_TENANCY); } - AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) tenantIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); try { - return storage.startTransaction(con -> { - String tenantId = tenantIdentifierWithStorage.getTenantId(); - AuthRecipeUserInfo userToAssociate = storage.getPrimaryUserById_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, userId); + return authRecipeStorage.startTransaction(con -> { + String tenantId = tenantIdentifier.getTenantId(); + AuthRecipeUserInfo userToAssociate = authRecipeStorage.getPrimaryUserById_Transaction(tenantIdentifier.toAppIdentifier(), con, userId); if (userToAssociate != null && userToAssociate.isPrimaryUser) { Set emails = new HashSet<>(); @@ -469,7 +471,7 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t } for (String email : emails) { - AuthRecipeUserInfo[] usersWithSameEmail = storage.listPrimaryUsersByEmail_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, email); + AuthRecipeUserInfo[] usersWithSameEmail = authRecipeStorage.listPrimaryUsersByEmail_Transaction(tenantIdentifier.toAppIdentifier(), con, email); for (AuthRecipeUserInfo userWithSameEmail : usersWithSameEmail) { if (userWithSameEmail.getSupertokensUserId().equals(userToAssociate.getSupertokensUserId())) { continue; // it's the same user, no need to check anything @@ -490,7 +492,7 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t } for (String phoneNumber : phoneNumbers) { - AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, phoneNumber); + AuthRecipeUserInfo[] usersWithSamePhoneNumber = authRecipeStorage.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier.toAppIdentifier(), con, phoneNumber); for (AuthRecipeUserInfo userWithSamePhoneNumber : usersWithSamePhoneNumber) { if (userWithSamePhoneNumber.getSupertokensUserId().equals(userToAssociate.getSupertokensUserId())) { continue; // it's the same user, no need to check anything @@ -511,7 +513,7 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t } for (LoginMethod.ThirdParty tp : thirdParties) { - AuthRecipeUserInfo[] usersWithSameThirdPartyInfo = storage.listPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, tp.id, tp.userId); + AuthRecipeUserInfo[] usersWithSameThirdPartyInfo = authRecipeStorage.listPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier.toAppIdentifier(), con, tp.id, tp.userId); for (AuthRecipeUserInfo userWithSameThirdPartyInfo : usersWithSameThirdPartyInfo) { if (userWithSameThirdPartyInfo.getSupertokensUserId().equals(userToAssociate.getSupertokensUserId())) { continue; // it's the same user, no need to check anything @@ -537,8 +539,8 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t // associate it. This happens only in CDI 3.0 where we allow disassociation from all tenants // This will not happen in CDI >= 4.0 because we will not allow disassociation from all tenants try { - boolean result = ((MultitenancySQLStorage) storage).addUserIdToTenant_Transaction(tenantIdentifierWithStorage, con, userId); - storage.commitTransaction(con); + boolean result = ((MultitenancySQLStorage) storage).addUserIdToTenant_Transaction(tenantIdentifier, con, userId); + authRecipeStorage.commitTransaction(con); return result; } catch (TenantOrAppNotFoundException | UnknownUserIdException | DuplicatePhoneNumberException | DuplicateThirdPartyUserException | DuplicateEmailException e) { @@ -567,7 +569,7 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t } } - public static boolean removeUserIdFromTenant(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static boolean removeUserIdFromTenant(Main main, TenantIdentifier tenantIdentifier, Storage storage, String userId, String externalUserId) throws FeatureNotEnabledException, TenantOrAppNotFoundException, StorageQueryException, UnknownUserIdException { @@ -577,12 +579,12 @@ public static boolean removeUserIdFromTenant(Main main, TenantIdentifierWithStor } boolean finalDidExist = false; - boolean didExist = AuthRecipe.deleteNonAuthRecipeUser(tenantIdentifierWithStorage, + boolean didExist = AuthRecipe.deleteNonAuthRecipeUser(tenantIdentifier, storage, externalUserId == null ? userId : externalUserId); finalDidExist = finalDidExist || didExist; - didExist = tenantIdentifierWithStorage.getMultitenancyStorageWithTargetStorage() - .removeUserIdFromTenant(tenantIdentifierWithStorage, userId); + didExist = StorageUtils.getMultitenancyStorage(storage) + .removeUserIdFromTenant(tenantIdentifier, userId); finalDidExist = finalDidExist || didExist; return finalDidExist; diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 6131006f0..73ed544d0 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -25,17 +25,19 @@ import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantConfig; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; @@ -71,14 +73,14 @@ public static CreateCodeResponse createCode(Main main, String email, String phon try { Storage storage = StorageLayer.getStorage(main); return createCode( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, main, email, phoneNumber, deviceId, userInputCode); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } - public static CreateCodeResponse createCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static CreateCodeResponse createCode(TenantIdentifier tenantIdentifier, Storage storage, Main main, String email, String phoneNumber, @Nullable String deviceId, @Nullable String userInputCode) @@ -86,20 +88,20 @@ public static CreateCodeResponse createCode(TenantIdentifierWithStorage tenantId StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException, TenantOrAppNotFoundException, BadPermissionException { - TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); + TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifier); if (config == null) { - throw new TenantOrAppNotFoundException(tenantIdentifierWithStorage); + throw new TenantOrAppNotFoundException(tenantIdentifier); } if (!config.passwordlessConfig.enabled) { throw new BadPermissionException("Passwordless login not enabled for tenant"); } - PasswordlessSQLStorage passwordlessStorage = tenantIdentifierWithStorage.getPasswordlessStorage(); + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); if (deviceId == null) { while (true) { CreateCodeInfo info = CreateCodeInfo.generate(userInputCode); try { - passwordlessStorage.createDeviceWithCode(tenantIdentifierWithStorage, email, phoneNumber, + passwordlessStorage.createDeviceWithCode(tenantIdentifier, email, phoneNumber, info.linkCodeSalt.encode(), info.code); return info.resp; @@ -112,7 +114,7 @@ public static CreateCodeResponse createCode(TenantIdentifierWithStorage tenantId } else { PasswordlessDeviceId parsedDeviceId = PasswordlessDeviceId.decodeString(deviceId); - PasswordlessDevice device = passwordlessStorage.getDevice(tenantIdentifierWithStorage, + PasswordlessDevice device = passwordlessStorage.getDevice(tenantIdentifier, parsedDeviceId.getHash().encode()); if (device == null) { throw new RestartFlowException(); @@ -120,7 +122,7 @@ public static CreateCodeResponse createCode(TenantIdentifierWithStorage tenantId while (true) { CreateCodeInfo info = CreateCodeInfo.generate(userInputCode, deviceId, device.linkCodeSalt); try { - passwordlessStorage.createCode(tenantIdentifierWithStorage, info.code); + passwordlessStorage.createCode(tenantIdentifier, info.code); return info.resp; } catch (DuplicateLinkCodeHashException e) { @@ -150,9 +152,9 @@ private static String generateUserInputCode() { } public static DeviceWithCodes getDeviceWithCodesById( - TenantIdentifierWithStorage tenantIdentifierWithStorage, String deviceId) + TenantIdentifier tenantIdentifier, Storage storage, String deviceId) throws StorageQueryException, NoSuchAlgorithmException, Base64EncodingException { - return getDeviceWithCodesByIdHash(tenantIdentifierWithStorage, + return getDeviceWithCodesByIdHash(tenantIdentifier, storage, PasswordlessDeviceId.decodeString(deviceId).getHash().encode()); } @@ -161,7 +163,7 @@ public static DeviceWithCodes getDeviceWithCodesById(Main main, String deviceId) NoSuchAlgorithmException, Base64EncodingException { Storage storage = StorageLayer.getStorage(main); return getDeviceWithCodesById( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, deviceId); } @@ -170,34 +172,34 @@ public static DeviceWithCodes getDeviceWithCodesByIdHash(Main main, String devic throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getDeviceWithCodesByIdHash( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, deviceIdHash); } - public static DeviceWithCodes getDeviceWithCodesByIdHash(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static DeviceWithCodes getDeviceWithCodesByIdHash(TenantIdentifier tenantIdentifier, Storage storage, String deviceIdHash) throws StorageQueryException { - PasswordlessSQLStorage passwordlessStorage = tenantIdentifierWithStorage.getPasswordlessStorage(); + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); - PasswordlessDevice device = passwordlessStorage.getDevice(tenantIdentifierWithStorage, deviceIdHash); + PasswordlessDevice device = passwordlessStorage.getDevice(tenantIdentifier, deviceIdHash); if (device == null) { return null; } - PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifierWithStorage, deviceIdHash); + PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifier, deviceIdHash); return new DeviceWithCodes(device, codes); } public static List getDevicesWithCodesByEmail( - TenantIdentifierWithStorage tenantIdentifierWithStorage, String email) + TenantIdentifier tenantIdentifier, Storage storage, String email) throws StorageQueryException { - PasswordlessSQLStorage passwordlessStorage = tenantIdentifierWithStorage.getPasswordlessStorage(); + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); - PasswordlessDevice[] devices = passwordlessStorage.getDevicesByEmail(tenantIdentifierWithStorage, email); + PasswordlessDevice[] devices = passwordlessStorage.getDevicesByEmail(tenantIdentifier, email); ArrayList result = new ArrayList(); for (PasswordlessDevice device : devices) { - PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifierWithStorage, + PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifier, device.deviceIdHash); result.add(new DeviceWithCodes(device, codes)); } @@ -210,19 +212,19 @@ public static List getDevicesWithCodesByEmail(Main main, String throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getDevicesWithCodesByEmail( - new TenantIdentifierWithStorage(null, null, null, storage), email); + new TenantIdentifier(null, null, null), storage, email); } public static List getDevicesWithCodesByPhoneNumber( - TenantIdentifierWithStorage tenantIdentifierWithStorage, String phoneNumber) + TenantIdentifier tenantIdentifier, Storage storage, String phoneNumber) throws StorageQueryException { - PasswordlessSQLStorage passwordlessStorage = tenantIdentifierWithStorage.getPasswordlessStorage(); + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); - PasswordlessDevice[] devices = passwordlessStorage.getDevicesByPhoneNumber(tenantIdentifierWithStorage, + PasswordlessDevice[] devices = passwordlessStorage.getDevicesByPhoneNumber(tenantIdentifier, phoneNumber); ArrayList result = new ArrayList(); for (PasswordlessDevice device : devices) { - PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifierWithStorage, + PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifier, device.deviceIdHash); result.add(new DeviceWithCodes(device, codes)); } @@ -235,7 +237,7 @@ public static List getDevicesWithCodesByPhoneNumber(Main main, throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getDevicesWithCodesByPhoneNumber( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, phoneNumber); } @@ -249,7 +251,7 @@ public static ConsumeCodeResponse consumeCode(Main main, try { Storage storage = StorageLayer.getStorage(main); return consumeCode( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, false, true); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); @@ -266,7 +268,7 @@ public static ConsumeCodeResponse consumeCode(Main main, try { Storage storage = StorageLayer.getStorage(main); return consumeCode( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, setEmailVerified, true); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); @@ -274,18 +276,18 @@ public static ConsumeCodeResponse consumeCode(Main main, } @TestOnly - public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, Storage storage, Main main, String deviceId, String deviceIdHashFromUser, String userInputCode, String linkCode) throws RestartFlowException, ExpiredUserInputCodeException, IncorrectUserInputCodeException, DeviceIdHashMismatchException, StorageTransactionLogicException, StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException, TenantOrAppNotFoundException, BadPermissionException { - return consumeCode(tenantIdentifierWithStorage, main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, + return consumeCode(tenantIdentifier, storage, main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, false, true); } - public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, Storage storage, Main main, String deviceId, String deviceIdHashFromUser, String userInputCode, String linkCode, boolean setEmailVerified, boolean createRecipeUserIfNotExists) throws RestartFlowException, ExpiredUserInputCodeException, @@ -293,18 +295,18 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException, TenantOrAppNotFoundException, BadPermissionException { - TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); + TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifier); if (config == null) { - throw new TenantOrAppNotFoundException(tenantIdentifierWithStorage); + throw new TenantOrAppNotFoundException(tenantIdentifier); } if (!config.passwordlessConfig.enabled) { throw new BadPermissionException("Passwordless login not enabled for tenant"); } - PasswordlessSQLStorage passwordlessStorage = tenantIdentifierWithStorage.getPasswordlessStorage(); - long passwordlessCodeLifetime = Config.getConfig(tenantIdentifierWithStorage, main) + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); + long passwordlessCodeLifetime = Config.getConfig(tenantIdentifier, main) .getPasswordlessCodeLifetime(); - int maxCodeInputAttempts = Config.getConfig(tenantIdentifierWithStorage, main) + int maxCodeInputAttempts = Config.getConfig(tenantIdentifier, main) .getPasswordlessMaxCodeInputAttempts(); PasswordlessDeviceIdHash deviceIdHash; @@ -313,7 +315,7 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant PasswordlessLinkCode parsedCode = PasswordlessLinkCode.decodeString(linkCode); linkCodeHash = parsedCode.getHash(); - PasswordlessCode code = passwordlessStorage.getCodeByLinkCodeHash(tenantIdentifierWithStorage, + PasswordlessCode code = passwordlessStorage.getCodeByLinkCodeHash(tenantIdentifier, linkCodeHash.encode()); if (code == null || code.createdAt < (System.currentTimeMillis() - passwordlessCodeLifetime)) { throw new RestartFlowException(); @@ -324,7 +326,7 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant PasswordlessDeviceId parsedDeviceId = PasswordlessDeviceId.decodeString(deviceId); deviceIdHash = parsedDeviceId.getHash(); - PasswordlessDevice device = passwordlessStorage.getDevice(tenantIdentifierWithStorage, + PasswordlessDevice device = passwordlessStorage.getDevice(tenantIdentifier, deviceIdHash.encode()); if (device == null) { throw new RestartFlowException(); @@ -340,21 +342,21 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant PasswordlessDevice consumedDevice; try { consumedDevice = passwordlessStorage.startTransaction(con -> { - PasswordlessDevice device = passwordlessStorage.getDevice_Transaction(tenantIdentifierWithStorage, con, + PasswordlessDevice device = passwordlessStorage.getDevice_Transaction(tenantIdentifier, con, deviceIdHash.encode()); if (device == null) { throw new StorageTransactionLogicException(new RestartFlowException()); } if (device.failedAttempts >= maxCodeInputAttempts) { // This can happen if the configured maxCodeInputAttempts changes - passwordlessStorage.deleteDevice_Transaction(tenantIdentifierWithStorage, con, + passwordlessStorage.deleteDevice_Transaction(tenantIdentifier, con, deviceIdHash.encode()); passwordlessStorage.commitTransaction(con); throw new StorageTransactionLogicException(new RestartFlowException()); } PasswordlessCode code = passwordlessStorage.getCodeByLinkCodeHash_Transaction( - tenantIdentifierWithStorage, con, + tenantIdentifier, con, linkCodeHash.encode()); if (code == null || code.createdAt < System.currentTimeMillis() - passwordlessCodeLifetime) { if (deviceId != null) { @@ -362,13 +364,13 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant // the code expired. This means that we need to increment failedAttempts or clean up the device // if it would exceed the configured max. if (device.failedAttempts + 1 >= maxCodeInputAttempts) { - passwordlessStorage.deleteDevice_Transaction(tenantIdentifierWithStorage, con, + passwordlessStorage.deleteDevice_Transaction(tenantIdentifier, con, deviceIdHash.encode()); passwordlessStorage.commitTransaction(con); throw new StorageTransactionLogicException(new RestartFlowException()); } else { passwordlessStorage.incrementDeviceFailedAttemptCount_Transaction( - tenantIdentifierWithStorage, con, + tenantIdentifier, con, deviceIdHash.encode()); passwordlessStorage.commitTransaction(con); @@ -385,10 +387,10 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } if (device.email != null) { - passwordlessStorage.deleteDevicesByEmail_Transaction(tenantIdentifierWithStorage, con, + passwordlessStorage.deleteDevicesByEmail_Transaction(tenantIdentifier, con, device.email); } else if (device.phoneNumber != null) { - passwordlessStorage.deleteDevicesByPhoneNumber_Transaction(tenantIdentifierWithStorage, con, + passwordlessStorage.deleteDevicesByPhoneNumber_Transaction(tenantIdentifier, con, device.phoneNumber); } @@ -412,11 +414,11 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant AuthRecipeUserInfo user = null; LoginMethod loginMethod = null; if (consumedDevice.email != null) { - AuthRecipeUserInfo[] users = passwordlessStorage.listPrimaryUsersByEmail(tenantIdentifierWithStorage, + AuthRecipeUserInfo[] users = passwordlessStorage.listPrimaryUsersByEmail(tenantIdentifier, consumedDevice.email); for (AuthRecipeUserInfo currUser : users) { for (LoginMethod currLM : currUser.loginMethods) { - if (currLM.recipeId == RECIPE_ID.PASSWORDLESS && currLM.email != null && currLM.email.equals(consumedDevice.email) && currLM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + if (currLM.recipeId == RECIPE_ID.PASSWORDLESS && currLM.email != null && currLM.email.equals(consumedDevice.email) && currLM.tenantIds.contains(tenantIdentifier.getTenantId())) { user = currUser; loginMethod = currLM; break; @@ -424,12 +426,12 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } } } else { - AuthRecipeUserInfo[] users = passwordlessStorage.listPrimaryUsersByPhoneNumber(tenantIdentifierWithStorage, + AuthRecipeUserInfo[] users = passwordlessStorage.listPrimaryUsersByPhoneNumber(tenantIdentifier, consumedDevice.phoneNumber); for (AuthRecipeUserInfo currUser : users) { for (LoginMethod currLM : currUser.loginMethods) { if (currLM.recipeId == RECIPE_ID.PASSWORDLESS && - currLM.phoneNumber != null && currLM.phoneNumber.equals(consumedDevice.phoneNumber) && currLM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + currLM.phoneNumber != null && currLM.phoneNumber.equals(consumedDevice.phoneNumber) && currLM.tenantIds.contains(tenantIdentifier.getTenantId())) { user = currUser; loginMethod = currLM; break; @@ -444,20 +446,20 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant try { String userId = Utils.getUUID(); long timeJoined = System.currentTimeMillis(); - user = passwordlessStorage.createUser(tenantIdentifierWithStorage, userId, consumedDevice.email, + 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; - tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { + EmailVerificationSQLStorage evStorage = + StorageUtils.getEmailVerificationStorage(storage); + evStorage.startTransaction(con -> { try { - tenantIdentifierWithStorage.getEmailVerificationStorage() - .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + evStorage.updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con, finalUser.getSupertokensUserId(), consumedDevice.email, true); - tenantIdentifierWithStorage.getEmailVerificationStorage() - .commitTransaction(con); + evStorage.commitTransaction(con); return null; } catch (TenantOrAppNotFoundException e) { @@ -494,13 +496,13 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant // Set email verification try { LoginMethod finalLoginMethod = loginMethod; - tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { + EmailVerificationSQLStorage evStorage = StorageUtils.getEmailVerificationStorage(storage); + evStorage.startTransaction(con -> { try { - tenantIdentifierWithStorage.getEmailVerificationStorage() - .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, - finalLoginMethod.getSupertokensUserId(), consumedDevice.email, true); - tenantIdentifierWithStorage.getEmailVerificationStorage() - .commitTransaction(con); + evStorage.updateIsEmailVerified_Transaction( + tenantIdentifier.toAppIdentifier(), con, finalLoginMethod.getSupertokensUserId(), + consumedDevice.email, true); + evStorage.commitTransaction(con); return null; } catch (TenantOrAppNotFoundException e) { @@ -517,10 +519,10 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } if (loginMethod.email != null && !loginMethod.email.equals(consumedDevice.email)) { - removeCodesByEmail(tenantIdentifierWithStorage, loginMethod.email); + removeCodesByEmail(tenantIdentifier, storage, loginMethod.email); } if (loginMethod.phoneNumber != null && !loginMethod.phoneNumber.equals(consumedDevice.phoneNumber)) { - removeCodesByPhoneNumber(tenantIdentifierWithStorage, loginMethod.phoneNumber); + removeCodesByPhoneNumber(tenantIdentifier, storage, loginMethod.phoneNumber); } } return new ConsumeCodeResponse(false, user, consumedDevice.email, consumedDevice.phoneNumber, consumedDevice); @@ -530,15 +532,15 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant public static void removeCode(Main main, String codeId) throws StorageQueryException, StorageTransactionLogicException { Storage storage = StorageLayer.getStorage(main); - removeCode(new TenantIdentifierWithStorage(null, null, null, storage), + removeCode(new TenantIdentifier(null, null, null), storage, codeId); } - public static void removeCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, String codeId) + public static void removeCode(TenantIdentifier tenantIdentifier, Storage storage, String codeId) throws StorageQueryException, StorageTransactionLogicException { - PasswordlessSQLStorage passwordlessStorage = tenantIdentifierWithStorage.getPasswordlessStorage(); + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); - PasswordlessCode code = passwordlessStorage.getCode(tenantIdentifierWithStorage, codeId); + PasswordlessCode code = passwordlessStorage.getCode(tenantIdentifier, codeId); if (code == null) { return; @@ -546,9 +548,9 @@ public static void removeCode(TenantIdentifierWithStorage tenantIdentifierWithSt passwordlessStorage.startTransaction(con -> { // Locking the device - passwordlessStorage.getDevice_Transaction(tenantIdentifierWithStorage, con, code.deviceIdHash); + passwordlessStorage.getDevice_Transaction(tenantIdentifier, con, code.deviceIdHash); - PasswordlessCode[] allCodes = passwordlessStorage.getCodesOfDevice_Transaction(tenantIdentifierWithStorage, + PasswordlessCode[] allCodes = passwordlessStorage.getCodesOfDevice_Transaction(tenantIdentifier, con, code.deviceIdHash); if (!Stream.of(allCodes).anyMatch(code::equals)) { @@ -558,10 +560,10 @@ public static void removeCode(TenantIdentifierWithStorage tenantIdentifierWithSt if (allCodes.length == 1) { // If the device contains only the current code we should delete the device as well. - passwordlessStorage.deleteDevice_Transaction(tenantIdentifierWithStorage, con, code.deviceIdHash); + passwordlessStorage.deleteDevice_Transaction(tenantIdentifier, con, code.deviceIdHash); } else { // Otherwise we can just delete the code - passwordlessStorage.deleteCode_Transaction(tenantIdentifierWithStorage, con, codeId); + passwordlessStorage.deleteCode_Transaction(tenantIdentifier, con, codeId); } passwordlessStorage.commitTransaction(con); return null; @@ -573,15 +575,15 @@ public static void removeCodesByEmail(Main main, String email) throws StorageQueryException, StorageTransactionLogicException { Storage storage = StorageLayer.getStorage(main); removeCodesByEmail( - new TenantIdentifierWithStorage(null, null, null, storage), email); + new TenantIdentifier(null, null, null), storage, email); } - public static void removeCodesByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, String email) + public static void removeCodesByEmail(TenantIdentifier tenantIdentifier, Storage storage, String email) throws StorageQueryException, StorageTransactionLogicException { - PasswordlessSQLStorage passwordlessStorage = tenantIdentifierWithStorage.getPasswordlessStorage(); + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); passwordlessStorage.startTransaction(con -> { - passwordlessStorage.deleteDevicesByEmail_Transaction(tenantIdentifierWithStorage, con, email); + passwordlessStorage.deleteDevicesByEmail_Transaction(tenantIdentifier, con, email); passwordlessStorage.commitTransaction(con); return null; }); @@ -593,17 +595,17 @@ public static void removeCodesByPhoneNumber(Main main, throws StorageQueryException, StorageTransactionLogicException { Storage storage = StorageLayer.getStorage(main); removeCodesByPhoneNumber( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, phoneNumber); } - public static void removeCodesByPhoneNumber(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static void removeCodesByPhoneNumber(TenantIdentifier tenantIdentifier, Storage storage, String phoneNumber) throws StorageQueryException, StorageTransactionLogicException { - PasswordlessSQLStorage passwordlessStorage = tenantIdentifierWithStorage.getPasswordlessStorage(); + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); passwordlessStorage.startTransaction(con -> { - passwordlessStorage.deleteDevicesByPhoneNumber_Transaction(tenantIdentifierWithStorage, con, phoneNumber); + passwordlessStorage.deleteDevicesByPhoneNumber_Transaction(tenantIdentifier, con, phoneNumber); passwordlessStorage.commitTransaction(con); return null; }); @@ -615,14 +617,14 @@ public static AuthRecipeUserInfo getUserById(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUserById( - new AppIdentifierWithStorage(null, null, storage), userId); + new AppIdentifier(null, null), storage, userId); } @Deprecated - public static AuthRecipeUserInfo getUserById(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static AuthRecipeUserInfo getUserById(AppIdentifier appIdentifier, Storage storage, String userId) throws StorageQueryException { - AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() - .getPrimaryUserById(appIdentifierWithStorage, userId); + AuthRecipeUserInfo result = StorageUtils.getAuthRecipeStorage(storage) + .getPrimaryUserById(appIdentifier, userId); if (result == null) { return null; } @@ -640,15 +642,15 @@ public static AuthRecipeUserInfo getUserByPhoneNumber(Main main, String phoneNumber) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUserByPhoneNumber( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, phoneNumber); } @Deprecated - public static AuthRecipeUserInfo getUserByPhoneNumber(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static AuthRecipeUserInfo getUserByPhoneNumber(TenantIdentifier tenantIdentifier, Storage storage, String phoneNumber) throws StorageQueryException { - AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getPasswordlessStorage() - .listPrimaryUsersByPhoneNumber(tenantIdentifierWithStorage, phoneNumber); + AuthRecipeUserInfo[] users = StorageUtils.getPasswordlessStorage(storage) + .listPrimaryUsersByPhoneNumber(tenantIdentifier, phoneNumber); for (AuthRecipeUserInfo user : users) { for (LoginMethod lM : user.loginMethods) { if (lM.recipeId == RECIPE_ID.PASSWORDLESS && lM.phoneNumber.equals(phoneNumber)) { @@ -665,15 +667,15 @@ public static AuthRecipeUserInfo getUserByEmail(Main main, String email) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUserByEmail( - new TenantIdentifierWithStorage(null, null, null, storage), email); + new TenantIdentifier(null, null, null), storage, email); } @Deprecated - public static AuthRecipeUserInfo getUserByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static AuthRecipeUserInfo getUserByEmail(TenantIdentifier tenantIdentifier, Storage storage, String email) throws StorageQueryException { - AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getPasswordlessStorage() - .listPrimaryUsersByEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] users = StorageUtils.getPasswordlessStorage(storage) + .listPrimaryUsersByEmail(tenantIdentifier, email); for (AuthRecipeUserInfo user : users) { for (LoginMethod lM : user.loginMethods) { if (lM.recipeId == RECIPE_ID.PASSWORDLESS && lM.email.equals(email)) { @@ -691,20 +693,20 @@ public static void updateUser(Main main, String userId, DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException, PhoneNumberChangeNotAllowedException { Storage storage = StorageLayer.getStorage(main); - updateUser(new AppIdentifierWithStorage(null, null, storage), + updateUser(new AppIdentifier(null, null), storage, userId, emailUpdate, phoneNumberUpdate); } - public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, String recipeUserId, + public static void updateUser(AppIdentifier appIdentifier, Storage storage, String recipeUserId, FieldUpdate emailUpdate, FieldUpdate phoneNumberUpdate) throws StorageQueryException, UnknownUserIdException, DuplicateEmailException, DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException, PhoneNumberChangeNotAllowedException { - PasswordlessSQLStorage storage = appIdentifierWithStorage.getPasswordlessStorage(); + PasswordlessSQLStorage plStorage = StorageUtils.getPasswordlessStorage(storage); // We do not lock the user here, because we decided that even if the device cleanup used outdated information // it wouldn't leave the system in an incosistent state/cause problems. - AuthRecipeUserInfo user = AuthRecipe.getUserById(appIdentifierWithStorage, recipeUserId); + AuthRecipeUserInfo user = AuthRecipe.getUserById(appIdentifier, storage, recipeUserId); if (user == null) { throw new UnknownUserIdException(); } @@ -724,15 +726,14 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, throw new UserWithoutContactInfoException(); } try { - AuthRecipeSQLStorage authRecipeSQLStorage = - (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); - storage.startTransaction(con -> { + AuthRecipeSQLStorage authRecipeSQLStorage = StorageUtils.getAuthRecipeStorage(storage); + plStorage.startTransaction(con -> { if (emailUpdate != null && !Objects.equals(emailUpdate.newValue, lM.email)) { if (user.isPrimaryUser) { for (String tenantId : user.tenantIds) { AuthRecipeUserInfo[] existingUsersWithNewEmail = authRecipeSQLStorage.listPrimaryUsersByEmail_Transaction( - appIdentifierWithStorage, con, + appIdentifier, con, emailUpdate.newValue); for (AuthRecipeUserInfo userWithSameEmail : existingUsersWithNewEmail) { @@ -747,17 +748,17 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, } } try { - storage.updateUserEmail_Transaction(appIdentifierWithStorage, con, recipeUserId, + plStorage.updateUserEmail_Transaction(appIdentifier, con, recipeUserId, emailUpdate.newValue); } catch (UnknownUserIdException | DuplicateEmailException e) { throw new StorageTransactionLogicException(e); } if (lM.email != null) { - storage.deleteDevicesByEmail_Transaction(appIdentifierWithStorage, con, lM.email, + plStorage.deleteDevicesByEmail_Transaction(appIdentifier, con, lM.email, recipeUserId); } if (emailUpdate.newValue != null) { - storage.deleteDevicesByEmail_Transaction(appIdentifierWithStorage, con, + plStorage.deleteDevicesByEmail_Transaction(appIdentifier, con, emailUpdate.newValue, recipeUserId); } } @@ -766,7 +767,7 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, for (String tenantId : user.tenantIds) { AuthRecipeUserInfo[] existingUsersWithNewPhoneNumber = authRecipeSQLStorage.listPrimaryUsersByPhoneNumber_Transaction( - appIdentifierWithStorage, con, + appIdentifier, con, phoneNumberUpdate.newValue); for (AuthRecipeUserInfo userWithSamePhoneNumber : existingUsersWithNewPhoneNumber) { @@ -781,21 +782,21 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, } } try { - storage.updateUserPhoneNumber_Transaction(appIdentifierWithStorage, con, recipeUserId, + plStorage.updateUserPhoneNumber_Transaction(appIdentifier, con, recipeUserId, phoneNumberUpdate.newValue); } catch (UnknownUserIdException | DuplicatePhoneNumberException e) { throw new StorageTransactionLogicException(e); } if (lM.phoneNumber != null) { - storage.deleteDevicesByPhoneNumber_Transaction(appIdentifierWithStorage, con, + plStorage.deleteDevicesByPhoneNumber_Transaction(appIdentifier, con, lM.phoneNumber, recipeUserId); } if (phoneNumberUpdate.newValue != null) { - storage.deleteDevicesByPhoneNumber_Transaction(appIdentifierWithStorage, con, + plStorage.deleteDevicesByPhoneNumber_Transaction(appIdentifier, con, phoneNumberUpdate.newValue, recipeUserId); } } - storage.commitTransaction(con); + plStorage.commitTransaction(con); return null; }); } catch (StorageTransactionLogicException e) { diff --git a/src/main/java/io/supertokens/session/Session.java b/src/main/java/io/supertokens/session/Session.java index eda332fcf..a20acbd14 100644 --- a/src/main/java/io/supertokens/session/Session.java +++ b/src/main/java/io/supertokens/session/Session.java @@ -29,12 +29,15 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.session.SessionStorage; import io.supertokens.pluginInterface.session.noSqlStorage.SessionNoSQLStorage_1; import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage; import io.supertokens.pluginInterface.sqlStorage.SQLStorage; @@ -64,7 +67,7 @@ public class Session { @TestOnly - public static SessionInformationHolder createNewSession(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static SessionInformationHolder createNewSession(TenantIdentifier tenantIdentifier, Storage storage, Main main, @Nonnull String recipeUserId, @Nonnull JsonObject userDataInJWT, @@ -74,9 +77,8 @@ public static SessionInformationHolder createNewSession(TenantIdentifierWithStor BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException, UnauthorisedException, JWT.JWTException, UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError { try { - return createNewSession(tenantIdentifierWithStorage, main, recipeUserId, userDataInJWT, userDataInDatabase, - false, - AccessToken.getLatestVersion(), false); + return createNewSession(tenantIdentifier, storage, main, recipeUserId, userDataInJWT, userDataInDatabase, + false, AccessToken.getLatestVersion(), false); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } @@ -94,7 +96,7 @@ public static SessionInformationHolder createNewSession(Main main, Storage storage = StorageLayer.getStorage(main); try { return createNewSession( - new TenantIdentifierWithStorage(null, null, null, storage), main, + new TenantIdentifier(null, null, null), storage, main, recipeUserId, userDataInJWT, userDataInDatabase, false, AccessToken.getLatestVersion(), false); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); @@ -114,14 +116,14 @@ public static SessionInformationHolder createNewSession(Main main, @Nonnull Stri Storage storage = StorageLayer.getStorage(main); try { return createNewSession( - new TenantIdentifierWithStorage(null, null, null, storage), main, + new TenantIdentifier(null, null, null), storage, main, recipeUserId, userDataInJWT, userDataInDatabase, enableAntiCsrf, version, useStaticKey); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static SessionInformationHolder createNewSession(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static SessionInformationHolder createNewSession(TenantIdentifier tenantIdentifier, Storage storage, Main main, @Nonnull String recipeUserId, @Nonnull JsonObject userDataInJWT, @Nonnull JsonObject userDataInDatabase, @@ -132,30 +134,30 @@ public static SessionInformationHolder createNewSession(TenantIdentifierWithStor BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException, AccessTokenPayloadError, UnsupportedJWTSigningAlgorithmException, TenantOrAppNotFoundException { String sessionHandle = UUID.randomUUID().toString(); - if (!tenantIdentifierWithStorage.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID)) { - sessionHandle += "_" + tenantIdentifierWithStorage.getTenantId(); + if (!tenantIdentifier.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID)) { + sessionHandle += "_" + tenantIdentifier.getTenantId(); } String primaryUserId = recipeUserId; - if (tenantIdentifierWithStorage.getStorage().getType().equals(STORAGE_TYPE.SQL)) { - primaryUserId = tenantIdentifierWithStorage.getAuthRecipeStorage() - .getPrimaryUserIdStrForUserId(tenantIdentifierWithStorage.toAppIdentifier(), recipeUserId); + if (storage.getType().equals(STORAGE_TYPE.SQL)) { + primaryUserId = StorageUtils.getAuthRecipeStorage(storage) + .getPrimaryUserIdStrForUserId(tenantIdentifier.toAppIdentifier(), recipeUserId); if (primaryUserId == null) { primaryUserId = recipeUserId; } } String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null; - final TokenInfo refreshToken = RefreshToken.createNewRefreshToken(tenantIdentifierWithStorage, main, + final TokenInfo refreshToken = RefreshToken.createNewRefreshToken(tenantIdentifier, main, sessionHandle, recipeUserId, null, antiCsrfToken); - TokenInfo accessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, sessionHandle, + TokenInfo accessToken = AccessToken.createNewAccessToken(tenantIdentifier, main, sessionHandle, recipeUserId, primaryUserId, Utils.hashSHA256(refreshToken.token), null, userDataInJWT, antiCsrfToken, null, version, useStaticKey); - tenantIdentifierWithStorage.getSessionStorage() - .createNewSession(tenantIdentifierWithStorage, sessionHandle, recipeUserId, + StorageUtils.getSessionStorage(storage) + .createNewSession(tenantIdentifier, sessionHandle, recipeUserId, Utils.hashSHA256(Utils.hashSHA256(refreshToken.token)), userDataInDatabase, refreshToken.expiry, userDataInJWT, refreshToken.createdTime, useStaticKey); @@ -163,7 +165,7 @@ public static SessionInformationHolder createNewSession(TenantIdentifierWithStor refreshToken.createdTime); return new SessionInformationHolder( new SessionInfo(sessionHandle, primaryUserId, recipeUserId, userDataInJWT, - tenantIdentifierWithStorage.getTenantId()), + tenantIdentifier.getTenantId()), accessToken, refreshToken, idRefreshToken, antiCsrfToken); } @@ -210,13 +212,13 @@ public static SessionInformationHolder regenerateToken(AppIdentifier appIdentifi // We assume the token has already been verified at this point. It may be expired or JWT signing key may have // changed for it... AccessTokenInfo accessToken = AccessToken.getInfoFromAccessTokenWithoutVerifying(appIdentifier, token); - TenantIdentifierWithStorage tenantIdentifierWithStorage = accessToken.tenantIdentifier.withStorage( - StorageLayer.getStorage(accessToken.tenantIdentifier, main)); - io.supertokens.pluginInterface.session.SessionInfo sessionInfo = getSession(tenantIdentifierWithStorage, + TenantIdentifier tenantIdentifier = accessToken.tenantIdentifier; + Storage storage = StorageLayer.getStorage(accessToken.tenantIdentifier, main); + io.supertokens.pluginInterface.session.SessionInfo sessionInfo = getSession(tenantIdentifier, storage, accessToken.sessionHandle); JsonObject newJWTUserPayload = userDataInJWT == null ? sessionInfo.userDataInJWT : userDataInJWT; - updateSession(tenantIdentifierWithStorage, accessToken.sessionHandle, null, newJWTUserPayload, + updateSession(tenantIdentifier, storage, accessToken.sessionHandle, null, newJWTUserPayload, accessToken.version); // if the above succeeds but the below fails, it's OK since the client will get server error and will try @@ -228,11 +230,11 @@ public static SessionInformationHolder regenerateToken(AppIdentifier appIdentifi return new SessionInformationHolder( new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, newJWTUserPayload, - tenantIdentifierWithStorage.getTenantId()), null, null, null, + tenantIdentifier.getTenantId()), null, null, null, null); } - TokenInfo newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, + TokenInfo newAccessToken = AccessToken.createNewAccessToken(tenantIdentifier, main, accessToken.sessionHandle, accessToken.recipeUserId, accessToken.primaryUserId, accessToken.refreshTokenHash1, accessToken.parentRefreshTokenHash1, newJWTUserPayload, accessToken.antiCsrfToken, accessToken.expiryTime, accessToken.version, sessionInfo.useStaticKey); @@ -240,7 +242,7 @@ public static SessionInformationHolder regenerateToken(AppIdentifier appIdentifi return new SessionInformationHolder( new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, newJWTUserPayload, - tenantIdentifierWithStorage.getTenantId()), + tenantIdentifier.getTenantId()), new TokenInfo(newAccessToken.token, newAccessToken.expiry, newAccessToken.createdTime), null, null, null); } @@ -258,14 +260,14 @@ public static SessionInformationHolder regenerateTokenBeforeCDI2_21(AppIdentifie // We assume the token has already been verified at this point. It may be expired or JWT signing key may have // changed for it... AccessTokenInfo accessToken = AccessToken.getInfoFromAccessTokenWithoutVerifying(appIdentifier, token); - TenantIdentifierWithStorage tenantIdentifierWithStorage = accessToken.tenantIdentifier.withStorage( - StorageLayer.getStorage(accessToken.tenantIdentifier, main)); - io.supertokens.pluginInterface.session.SessionInfo sessionInfo = getSession(tenantIdentifierWithStorage, + TenantIdentifier tenantIdentifier = accessToken.tenantIdentifier; + Storage storage = StorageLayer.getStorage(accessToken.tenantIdentifier, main); + io.supertokens.pluginInterface.session.SessionInfo sessionInfo = getSession(tenantIdentifier, storage, accessToken.sessionHandle); JsonObject newJWTUserPayload = userDataInJWT == null ? sessionInfo.userDataInJWT : userDataInJWT; updateSessionBeforeCDI2_21( - tenantIdentifierWithStorage, + tenantIdentifier, storage, accessToken.sessionHandle, null, newJWTUserPayload); // if the above succeeds but the below fails, it's OK since the client will get server error and will try @@ -277,7 +279,7 @@ public static SessionInformationHolder regenerateTokenBeforeCDI2_21(AppIdentifie return new SessionInformationHolder( new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, newJWTUserPayload, - tenantIdentifierWithStorage.getTenantId()), null, null, null, + tenantIdentifier.getTenantId()), null, null, null, null); } @@ -290,7 +292,7 @@ public static SessionInformationHolder regenerateTokenBeforeCDI2_21(AppIdentifie return new SessionInformationHolder( new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, newJWTUserPayload, - tenantIdentifierWithStorage.getTenantId()), + tenantIdentifier.getTenantId()), new TokenInfo(newAccessToken.token, newAccessToken.expiry, newAccessToken.createdTime), null, null, null); } @@ -320,8 +322,8 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M AccessTokenInfo accessToken = AccessToken.getInfoFromAccessToken(appIdentifier, main, token, doAntiCsrfCheck && enableAntiCsrf); - TenantIdentifierWithStorage tenantIdentifierWithStorage = accessToken.tenantIdentifier.withStorage( - StorageLayer.getStorage(accessToken.tenantIdentifier, main)); + TenantIdentifier tenantIdentifier = accessToken.tenantIdentifier; + Storage storage = StorageLayer.getStorage(accessToken.tenantIdentifier, main); if (enableAntiCsrf && doAntiCsrfCheck && (antiCsrfToken == null || !antiCsrfToken.equals(accessToken.antiCsrfToken))) { @@ -330,8 +332,8 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M io.supertokens.pluginInterface.session.SessionInfo sessionInfoForBlacklisting = null; if (checkDatabase) { - sessionInfoForBlacklisting = tenantIdentifierWithStorage.getSessionStorage() - .getSession(tenantIdentifierWithStorage, accessToken.sessionHandle); + sessionInfoForBlacklisting = StorageUtils.getSessionStorage(storage) + .getSession(tenantIdentifier, accessToken.sessionHandle); if (sessionInfoForBlacklisting == null) { throw new UnauthorisedException("Either the session has ended or has been blacklisted"); } @@ -345,25 +347,25 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M return new SessionInformationHolder( new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, accessToken.userData, - tenantIdentifierWithStorage.getTenantId()), null, null, + tenantIdentifier.getTenantId()), null, null, null, null); } ProcessState.getInstance(main).addState(ProcessState.PROCESS_STATE.GET_SESSION_NEW_TOKENS, null); - if (tenantIdentifierWithStorage.getSessionStorage().getType() == STORAGE_TYPE.SQL) { - SessionSQLStorage storage = (SessionSQLStorage) tenantIdentifierWithStorage.getSessionStorage(); + if (StorageUtils.getSessionStorage(storage).getType() == STORAGE_TYPE.SQL) { + SessionSQLStorage sessionStorage = (SessionSQLStorage) StorageUtils.getSessionStorage(storage); try { - CoreConfig config = Config.getConfig(tenantIdentifierWithStorage, main); - return storage.startTransaction(con -> { + CoreConfig config = Config.getConfig(tenantIdentifier, main); + return sessionStorage.startTransaction(con -> { try { - io.supertokens.pluginInterface.session.SessionInfo sessionInfo = storage - .getSessionInfo_Transaction(tenantIdentifierWithStorage, con, + io.supertokens.pluginInterface.session.SessionInfo sessionInfo = sessionStorage + .getSessionInfo_Transaction(tenantIdentifier, con, accessToken.sessionHandle); if (sessionInfo == null) { - storage.commitTransaction(con); + sessionStorage.commitTransaction(con); throw new UnauthorisedException("Session missing in db"); } @@ -373,23 +375,23 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M || sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(accessToken.refreshTokenHash1)) || JWTPayloadNeedsUpdating) { if (promote) { - storage.updateSessionInfo_Transaction(tenantIdentifierWithStorage, con, + sessionStorage.updateSessionInfo_Transaction(tenantIdentifier, con, accessToken.sessionHandle, Utils.hashSHA256(accessToken.refreshTokenHash1), System.currentTimeMillis() + config.getRefreshTokenValidity(), sessionInfo.useStaticKey); } - storage.commitTransaction(con); + sessionStorage.commitTransaction(con); TokenInfo newAccessToken; if (AccessToken.getAccessTokenVersion(accessToken) == AccessToken.VERSION.V1) { - newAccessToken = AccessToken.createNewAccessTokenV1(tenantIdentifierWithStorage, + newAccessToken = AccessToken.createNewAccessTokenV1(tenantIdentifier, main, accessToken.sessionHandle, accessToken.recipeUserId, accessToken.refreshTokenHash1, null, sessionInfo.userDataInJWT, accessToken.antiCsrfToken); } else { - newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, + newAccessToken = AccessToken.createNewAccessToken(tenantIdentifier, main, accessToken.sessionHandle, accessToken.recipeUserId, accessToken.primaryUserId, accessToken.refreshTokenHash1, null, @@ -400,17 +402,17 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M return new SessionInformationHolder( new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, - sessionInfo.userDataInJWT, tenantIdentifierWithStorage.getTenantId()), + sessionInfo.userDataInJWT, tenantIdentifier.getTenantId()), new TokenInfo(newAccessToken.token, newAccessToken.expiry, newAccessToken.createdTime), null, null, null); } - storage.commitTransaction(con); + sessionStorage.commitTransaction(con); return new SessionInformationHolder( new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, accessToken.userData, - tenantIdentifierWithStorage.getTenantId()), + tenantIdentifier.getTenantId()), // here we purposely use accessToken.userData instead of sessionInfo.userDataInJWT // because we are not returning a new access token null, null, null, null); @@ -432,13 +434,13 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M } throw e; } - } else if (tenantIdentifierWithStorage.getSessionStorage().getType() == + } else if (StorageUtils.getSessionStorage(storage).getType() == STORAGE_TYPE.NOSQL_1) { - SessionNoSQLStorage_1 storage = (SessionNoSQLStorage_1) tenantIdentifierWithStorage.getSessionStorage(); + SessionNoSQLStorage_1 sessionStorage = (SessionNoSQLStorage_1) StorageUtils.getSessionStorage(storage); while (true) { try { - io.supertokens.pluginInterface.session.noSqlStorage.SessionInfoWithLastUpdated sessionInfo = storage + io.supertokens.pluginInterface.session.noSqlStorage.SessionInfoWithLastUpdated sessionInfo = sessionStorage .getSessionInfo_Transaction(accessToken.sessionHandle); if (sessionInfo == null) { @@ -450,9 +452,9 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M if (promote || sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(accessToken.refreshTokenHash1)) || JWTPayloadNeedsUpdating) { if (promote) { - boolean success = storage.updateSessionInfo_Transaction(accessToken.sessionHandle, + boolean success = sessionStorage.updateSessionInfo_Transaction(accessToken.sessionHandle, Utils.hashSHA256(accessToken.refreshTokenHash1), - System.currentTimeMillis() + Config.getConfig(tenantIdentifierWithStorage, main) + System.currentTimeMillis() + Config.getConfig(tenantIdentifier, main) .getRefreshTokenValidity(), sessionInfo.lastUpdatedSign, sessionInfo.useStaticKey); if (!success) { @@ -462,13 +464,13 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M TokenInfo newAccessToken; if (accessToken.version == AccessToken.VERSION.V1) { - newAccessToken = AccessToken.createNewAccessTokenV1(tenantIdentifierWithStorage, main, + newAccessToken = AccessToken.createNewAccessTokenV1(tenantIdentifier, main, accessToken.sessionHandle, accessToken.recipeUserId, accessToken.refreshTokenHash1, null, sessionInfo.userDataInJWT, accessToken.antiCsrfToken); } else { - newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, + newAccessToken = AccessToken.createNewAccessToken(tenantIdentifier, main, accessToken.sessionHandle, accessToken.recipeUserId, accessToken.primaryUserId, accessToken.refreshTokenHash1, null, sessionInfo.userDataInJWT, @@ -478,7 +480,7 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M return new SessionInformationHolder( new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, - sessionInfo.userDataInJWT, tenantIdentifierWithStorage.getTenantId()), + sessionInfo.userDataInJWT, tenantIdentifier.getTenantId()), new TokenInfo(newAccessToken.token, newAccessToken.expiry, newAccessToken.createdTime), null, null, null); } @@ -486,7 +488,7 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M return new SessionInformationHolder( new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, accessToken.userData, - tenantIdentifierWithStorage.getTenantId()), + tenantIdentifier.getTenantId()), // here we purposely use accessToken.userData instead of sessionInfo.userDataInJWT // because we are not returning a new access token null, null, null, null); @@ -532,13 +534,14 @@ public static SessionInformationHolder refreshSession(AppIdentifier appIdentifie } } - return refreshSessionHelper(refreshTokenInfo.tenantIdentifier.withStorage( - StorageLayer.getStorage(refreshTokenInfo.tenantIdentifier, main)), - main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion, shouldUseStaticKey); + TenantIdentifier tenantIdentifier = refreshTokenInfo.tenantIdentifier; + Storage storage = StorageLayer.getStorage(refreshTokenInfo.tenantIdentifier, main); + return refreshSessionHelper( + tenantIdentifier, storage, main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion, shouldUseStaticKey); } private static SessionInformationHolder refreshSessionHelper( - TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String refreshToken, + TenantIdentifier tenantIdentifier, Storage storage, Main main, String refreshToken, RefreshToken.RefreshTokenInfo refreshTokenInfo, boolean enableAntiCsrf, AccessToken.VERSION accessTokenVersion, Boolean shouldUseStaticKey) @@ -551,18 +554,18 @@ private static SessionInformationHolder refreshSessionHelper( ////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// - if (tenantIdentifierWithStorage.getSessionStorage().getType() == STORAGE_TYPE.SQL) { - SessionSQLStorage storage = (SessionSQLStorage) tenantIdentifierWithStorage.getSessionStorage(); + if (StorageUtils.getSessionStorage(storage).getType() == STORAGE_TYPE.SQL) { + SessionSQLStorage sessionStorage = (SessionSQLStorage) StorageUtils.getSessionStorage(storage); try { - CoreConfig config = Config.getConfig(tenantIdentifierWithStorage, main); - return storage.startTransaction(con -> { + CoreConfig config = Config.getConfig(tenantIdentifier, main); + return sessionStorage.startTransaction(con -> { try { String sessionHandle = refreshTokenInfo.sessionHandle; - io.supertokens.pluginInterface.session.SessionInfo sessionInfo = storage - .getSessionInfo_Transaction(tenantIdentifierWithStorage, con, sessionHandle); + io.supertokens.pluginInterface.session.SessionInfo sessionInfo = sessionStorage + .getSessionInfo_Transaction(tenantIdentifier, con, sessionHandle); if (sessionInfo == null || sessionInfo.expiry < System.currentTimeMillis()) { - storage.commitTransaction(con); + sessionStorage.commitTransaction(con); throw new UnauthorisedException("Session missing in db or has expired"); } boolean useStaticKey = shouldUseStaticKey != null ? shouldUseStaticKey : sessionInfo.useStaticKey; @@ -570,20 +573,20 @@ private static SessionInformationHolder refreshSessionHelper( if (sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(Utils.hashSHA256(refreshToken)))) { if (useStaticKey != sessionInfo.useStaticKey) { // We do not update anything except the static key status - storage.updateSessionInfo_Transaction(tenantIdentifierWithStorage, con, sessionHandle, + sessionStorage.updateSessionInfo_Transaction(tenantIdentifier, con, sessionHandle, sessionInfo.refreshTokenHash2, sessionInfo.expiry, useStaticKey); } // at this point, the input refresh token is the parent one. - storage.commitTransaction(con); + sessionStorage.commitTransaction(con); String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null; final TokenInfo newRefreshToken = RefreshToken.createNewRefreshToken( - tenantIdentifierWithStorage, main, sessionHandle, + tenantIdentifier, main, sessionHandle, sessionInfo.recipeUserId, Utils.hashSHA256(refreshToken), antiCsrfToken); - TokenInfo newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, + TokenInfo newAccessToken = AccessToken.createNewAccessToken(tenantIdentifier, main, sessionHandle, sessionInfo.recipeUserId, sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token), @@ -597,7 +600,7 @@ private static SessionInformationHolder refreshSessionHelper( return new SessionInformationHolder( new SessionInfo(sessionHandle, sessionInfo.userId, sessionInfo.recipeUserId, sessionInfo.userDataInJWT, - tenantIdentifierWithStorage.getTenantId()), + tenantIdentifier.getTenantId()), newAccessToken, newRefreshToken, idRefreshToken, antiCsrfToken); } @@ -607,18 +610,18 @@ private static SessionInformationHolder refreshSessionHelper( || (refreshTokenInfo.parentRefreshTokenHash1 != null && Utils.hashSHA256(refreshTokenInfo.parentRefreshTokenHash1) .equals(sessionInfo.refreshTokenHash2))) { - storage.updateSessionInfo_Transaction(tenantIdentifierWithStorage, con, sessionHandle, + sessionStorage.updateSessionInfo_Transaction(tenantIdentifier, con, sessionHandle, Utils.hashSHA256(Utils.hashSHA256(refreshToken)), System.currentTimeMillis() + config.getRefreshTokenValidity(), useStaticKey); - storage.commitTransaction(con); + sessionStorage.commitTransaction(con); - return refreshSessionHelper(tenantIdentifierWithStorage, main, refreshToken, + return refreshSessionHelper(tenantIdentifier, storage, main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion, shouldUseStaticKey); } - storage.commitTransaction(con); + sessionStorage.commitTransaction(con); throw new TokenTheftDetectedException(sessionHandle, sessionInfo.recipeUserId, sessionInfo.userId); @@ -651,13 +654,13 @@ private static SessionInformationHolder refreshSessionHelper( ////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// - } else if (tenantIdentifierWithStorage.getSessionStorage().getType() == + } else if (StorageUtils.getSessionStorage(storage).getType() == STORAGE_TYPE.NOSQL_1) { - SessionNoSQLStorage_1 storage = (SessionNoSQLStorage_1) tenantIdentifierWithStorage.getSessionStorage(); + SessionNoSQLStorage_1 sessionStorage = (SessionNoSQLStorage_1) StorageUtils.getSessionStorage(storage); while (true) { try { String sessionHandle = refreshTokenInfo.sessionHandle; - io.supertokens.pluginInterface.session.noSqlStorage.SessionInfoWithLastUpdated sessionInfo = storage + io.supertokens.pluginInterface.session.noSqlStorage.SessionInfoWithLastUpdated sessionInfo = sessionStorage .getSessionInfo_Transaction(sessionHandle); if (sessionInfo == null || sessionInfo.expiry < System.currentTimeMillis()) { @@ -669,7 +672,7 @@ private static SessionInformationHolder refreshSessionHelper( if (sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(Utils.hashSHA256(refreshToken)))) { if (sessionInfo.useStaticKey != useStaticKey) { // We do not update anything except the static key status - boolean success = storage.updateSessionInfo_Transaction(sessionHandle, + boolean success = sessionStorage.updateSessionInfo_Transaction(sessionHandle, sessionInfo.refreshTokenHash2, sessionInfo.expiry, sessionInfo.lastUpdatedSign, useStaticKey); if (!success) { @@ -680,9 +683,9 @@ private static SessionInformationHolder refreshSessionHelper( String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null; final TokenInfo newRefreshToken = RefreshToken.createNewRefreshToken( - tenantIdentifierWithStorage, main, sessionHandle, + tenantIdentifier, main, sessionHandle, sessionInfo.recipeUserId, Utils.hashSHA256(refreshToken), antiCsrfToken); - TokenInfo newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, + TokenInfo newAccessToken = AccessToken.createNewAccessToken(tenantIdentifier, main, sessionHandle, sessionInfo.recipeUserId, sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token), Utils.hashSHA256(refreshToken), sessionInfo.userDataInJWT, antiCsrfToken, @@ -695,7 +698,7 @@ private static SessionInformationHolder refreshSessionHelper( return new SessionInformationHolder( new SessionInfo(sessionHandle, sessionInfo.userId, sessionInfo.recipeUserId, sessionInfo.userDataInJWT, - tenantIdentifierWithStorage.getTenantId()), + tenantIdentifier.getTenantId()), newAccessToken, newRefreshToken, idRefreshToken, antiCsrfToken); } @@ -705,15 +708,16 @@ private static SessionInformationHolder refreshSessionHelper( || (refreshTokenInfo.parentRefreshTokenHash1 != null && Utils.hashSHA256(refreshTokenInfo.parentRefreshTokenHash1) .equals(sessionInfo.refreshTokenHash2))) { - boolean success = storage.updateSessionInfo_Transaction(sessionHandle, + boolean success = sessionStorage.updateSessionInfo_Transaction(sessionHandle, Utils.hashSHA256(Utils.hashSHA256(refreshToken)), System.currentTimeMillis() + - Config.getConfig(tenantIdentifierWithStorage, main).getRefreshTokenValidity(), + Config.getConfig(tenantIdentifier, main).getRefreshTokenValidity(), sessionInfo.lastUpdatedSign, useStaticKey); if (!success) { continue; } - return refreshSessionHelper(tenantIdentifierWithStorage, main, refreshToken, refreshTokenInfo, + return refreshSessionHelper( + tenantIdentifier, storage, main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion, shouldUseStaticKey); } @@ -737,12 +741,13 @@ public static String[] revokeSessionUsingSessionHandles(Main main, throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return revokeSessionUsingSessionHandles(main, - new AppIdentifierWithStorage(null, null, storage), + new AppIdentifier(null, null), storage, sessionHandles); } public static String[] revokeSessionUsingSessionHandles(Main main, - AppIdentifierWithStorage appIdentifierWithStorage, + AppIdentifier appIdentifier, + Storage storage, String[] sessionHandles) throws StorageQueryException { @@ -765,18 +770,17 @@ public static String[] revokeSessionUsingSessionHandles(Main main, for (String tenantId : sessionHandleMap.keySet()) { String[] sessionHandlesForTenant = sessionHandleMap.get(tenantId).toArray(new String[0]); - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifierWithStorage.getConnectionUriDomain(), - appIdentifierWithStorage.getAppId(), tenantId); - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), + appIdentifier.getAppId(), tenantId); + Storage tenantStorage = null; try { - tenantIdentifierWithStorage = tenantIdentifier.withStorage( - StorageLayer.getStorage(tenantIdentifier, main)); + tenantStorage = StorageLayer.getStorage(tenantIdentifier, main); } catch (TenantOrAppNotFoundException e) { // ignore as this can happen if the tenant has been deleted after fetching the sessionHandles continue; } - String[] sessionHandlesRevokedForTenant = revokeSessionUsingSessionHandles(tenantIdentifierWithStorage, + String[] sessionHandlesRevokedForTenant = revokeSessionUsingSessionHandles(tenantIdentifier, tenantStorage, sessionHandlesForTenant); revokedSessionHandles.addAll(Arrays.asList(sessionHandlesRevokedForTenant)); } @@ -784,7 +788,8 @@ public static String[] revokeSessionUsingSessionHandles(Main main, return revokedSessionHandles.toArray(new String[0]); } - private static String[] revokeSessionUsingSessionHandles(TenantIdentifierWithStorage tenantIdentifierWithStorage, + private static String[] revokeSessionUsingSessionHandles(TenantIdentifier tenantIdentifier, + Storage storage, String[] sessionHandles) throws StorageQueryException { Set validHandles = new HashSet<>(); @@ -794,15 +799,15 @@ private static String[] revokeSessionUsingSessionHandles(TenantIdentifierWithSto // if there is only one sessionHandle to revoke, we would know if it was valid by the number of revoked // sessions for (String sessionHandle : sessionHandles) { - if (tenantIdentifierWithStorage.getSessionStorage() - .getSession(tenantIdentifierWithStorage, sessionHandle) != null) { + if (((SessionStorage) storage) + .getSession(tenantIdentifier, sessionHandle) != null) { validHandles.add(sessionHandle); } } } - int numberOfSessionsRevoked = tenantIdentifierWithStorage.getSessionStorage() - .deleteSession(tenantIdentifierWithStorage, sessionHandles); + int numberOfSessionsRevoked = ((SessionStorage) storage) + .deleteSession(tenantIdentifier, sessionHandles); // most of the time we will enter the below if statement if (numberOfSessionsRevoked == sessionHandles.length) { @@ -815,8 +820,8 @@ private static String[] revokeSessionUsingSessionHandles(TenantIdentifierWithSto if (!validHandles.contains(sessionHandle)) { continue; // no need to check if the sessionHandle was invalid in the first place } - if (tenantIdentifierWithStorage.getSessionStorage() - .getSession(tenantIdentifierWithStorage, sessionHandle) == null) { + if (((SessionStorage) storage) + .getSession(tenantIdentifier, sessionHandle) == null) { revokedSessionHandles.add(sessionHandle); } } @@ -828,23 +833,24 @@ private static String[] revokeSessionUsingSessionHandles(TenantIdentifierWithSto public static String[] revokeAllSessionsForUser(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return revokeAllSessionsForUser(main, - new AppIdentifierWithStorage(null, null, storage), userId, true); + new AppIdentifier(null, null), storage, userId, true); } - public static String[] revokeAllSessionsForUser(Main main, AppIdentifierWithStorage appIdentifierWithStorage, - String userId, boolean revokeSessionsForLinkedAccounts) + public static String[] revokeAllSessionsForUser(Main main, AppIdentifier appIdentifier, + Storage storage, String userId, + boolean revokeSessionsForLinkedAccounts) throws StorageQueryException { - String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(main, appIdentifierWithStorage, userId, + String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(main, appIdentifier, storage, userId, revokeSessionsForLinkedAccounts); - return revokeSessionUsingSessionHandles(main, appIdentifierWithStorage, sessionHandles); + return revokeSessionUsingSessionHandles(main, appIdentifier, storage, sessionHandles); } - public static String[] revokeAllSessionsForUser(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static String[] revokeAllSessionsForUser(Main main, TenantIdentifier tenantIdentifier, Storage storage, String userId, boolean revokeSessionsForLinkedAccounts) throws StorageQueryException { - String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(tenantIdentifierWithStorage, userId, + String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(tenantIdentifier, storage, userId, revokeSessionsForLinkedAccounts); - return revokeSessionUsingSessionHandles(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), + return revokeSessionUsingSessionHandles(main, tenantIdentifier.toAppIdentifier(), storage, sessionHandles); } @@ -853,24 +859,24 @@ public static String[] getAllNonExpiredSessionHandlesForUser(Main main, String u throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getAllNonExpiredSessionHandlesForUser(main, - new AppIdentifierWithStorage(null, null, storage), userId, true); + new AppIdentifier(null, null), storage, userId, true); } public static String[] getAllNonExpiredSessionHandlesForUser( - Main main, AppIdentifierWithStorage appIdentifierWithStorage, String userId, + Main main, AppIdentifier appIdentifier, Storage storage, String userId, boolean fetchSessionsForAllLinkedAccounts) throws StorageQueryException { TenantConfig[] tenants = Multitenancy.getAllTenantsForApp( - appIdentifierWithStorage, main); + appIdentifier, main); List sessionHandles = new ArrayList<>(); Set userIds = new HashSet<>(); userIds.add(userId); if (fetchSessionsForAllLinkedAccounts) { - if (appIdentifierWithStorage.getStorage().getType().equals(STORAGE_TYPE.SQL)) { - AuthRecipeUserInfo primaryUser = appIdentifierWithStorage.getAuthRecipeStorage() - .getPrimaryUserById(appIdentifierWithStorage, userId); + if (storage.getType().equals(STORAGE_TYPE.SQL)) { + AuthRecipeUserInfo primaryUser = ((AuthRecipeStorage) storage) + .getPrimaryUserById(appIdentifier, userId); if (primaryUser != null) { for (LoginMethod lM : primaryUser.loginMethods) { userIds.add(lM.getSupertokensUserId()); @@ -881,12 +887,10 @@ public static String[] getAllNonExpiredSessionHandlesForUser( for (String currUserId : userIds) { for (TenantConfig tenant : tenants) { - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; try { - tenantIdentifierWithStorage = tenant.tenantIdentifier.withStorage( - StorageLayer.getStorage(tenant.tenantIdentifier, main)); sessionHandles.addAll(Arrays.asList(getAllNonExpiredSessionHandlesForUser( - tenantIdentifierWithStorage, currUserId, false))); + tenant.tenantIdentifier, StorageLayer.getStorage(tenant.tenantIdentifier, main), + currUserId, false))); } catch (TenantOrAppNotFoundException e) { // this might happen when a tenant was deleted after the tenant list was fetched @@ -899,14 +903,14 @@ public static String[] getAllNonExpiredSessionHandlesForUser( } public static String[] getAllNonExpiredSessionHandlesForUser( - TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId, + TenantIdentifier tenantIdentifier, Storage storage, String userId, boolean fetchSessionsForAllLinkedAccounts) throws StorageQueryException { Set userIds = new HashSet<>(); userIds.add(userId); if (fetchSessionsForAllLinkedAccounts) { - AuthRecipeUserInfo primaryUser = tenantIdentifierWithStorage.getAuthRecipeStorage() - .getPrimaryUserById(tenantIdentifierWithStorage.toAppIdentifier(), userId); + AuthRecipeUserInfo primaryUser = ((AuthRecipeStorage) storage) + .getPrimaryUserById(tenantIdentifier.toAppIdentifier(), userId); if (primaryUser != null) { for (LoginMethod lM : primaryUser.loginMethods) { userIds.add(lM.getSupertokensUserId()); @@ -915,8 +919,8 @@ public static String[] getAllNonExpiredSessionHandlesForUser( } List sessionHandles = new ArrayList<>(); for (String currUserId : userIds) { - sessionHandles.addAll(List.of(tenantIdentifierWithStorage.getSessionStorage() - .getAllNonExpiredSessionHandlesForUser(tenantIdentifierWithStorage, currUserId))); + sessionHandles.addAll(List.of(((SessionStorage) storage) + .getAllNonExpiredSessionHandlesForUser(tenantIdentifier, currUserId))); } return sessionHandles.toArray(new String[0]); } @@ -926,16 +930,16 @@ public static JsonObject getSessionData(Main main, String sessionHandle) throws StorageQueryException, UnauthorisedException { Storage storage = StorageLayer.getStorage(main); return getSessionData( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, sessionHandle); } @Deprecated - public static JsonObject getSessionData(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static JsonObject getSessionData(TenantIdentifier tenantIdentifier, Storage storage, String sessionHandle) throws StorageQueryException, UnauthorisedException { - io.supertokens.pluginInterface.session.SessionInfo session = tenantIdentifierWithStorage.getSessionStorage() - .getSession(tenantIdentifierWithStorage, sessionHandle); + io.supertokens.pluginInterface.session.SessionInfo session = StorageUtils.getSessionStorage(storage) + .getSession(tenantIdentifier, sessionHandle); if (session == null || session.expiry <= System.currentTimeMillis()) { throw new UnauthorisedException("Session does not exist."); } @@ -947,15 +951,15 @@ public static JsonObject getJWTData(Main main, String sessionHandle) throws StorageQueryException, UnauthorisedException { Storage storage = StorageLayer.getStorage(main); return getJWTData( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, sessionHandle); } @Deprecated - public static JsonObject getJWTData(TenantIdentifierWithStorage tenantIdentifierWithStorage, String sessionHandle) + public static JsonObject getJWTData(TenantIdentifier tenantIdentifier, Storage storage, String sessionHandle) throws StorageQueryException, UnauthorisedException { - io.supertokens.pluginInterface.session.SessionInfo session = tenantIdentifierWithStorage.getSessionStorage() - .getSession(tenantIdentifierWithStorage, sessionHandle); + io.supertokens.pluginInterface.session.SessionInfo session = StorageUtils.getSessionStorage(storage) + .getSession(tenantIdentifier, sessionHandle); if (session == null || session.expiry <= System.currentTimeMillis()) { throw new UnauthorisedException("Session does not exist."); } @@ -967,7 +971,7 @@ public static io.supertokens.pluginInterface.session.SessionInfo getSession(Main throws StorageQueryException, UnauthorisedException { Storage storage = StorageLayer.getStorage(main); return getSession( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, sessionHandle); } @@ -977,10 +981,10 @@ public static io.supertokens.pluginInterface.session.SessionInfo getSession(Main * - /recipe/session GET */ public static io.supertokens.pluginInterface.session.SessionInfo getSession( - TenantIdentifierWithStorage tenantIdentifierWithStorage, String sessionHandle) + TenantIdentifier tenantIdentifier, Storage storage, String sessionHandle) throws StorageQueryException, UnauthorisedException { - io.supertokens.pluginInterface.session.SessionInfo session = tenantIdentifierWithStorage.getSessionStorage() - .getSession(tenantIdentifierWithStorage, sessionHandle); + io.supertokens.pluginInterface.session.SessionInfo session = StorageUtils.getSessionStorage(storage) + .getSession(tenantIdentifier, sessionHandle); // If there is no session, or session is expired if (session == null || session.expiry <= System.currentTimeMillis()) { @@ -997,11 +1001,11 @@ public static void updateSession(Main main, String sessionHandle, AccessToken.VERSION version) throws StorageQueryException, UnauthorisedException, AccessTokenPayloadError { Storage storage = StorageLayer.getStorage(main); - updateSession(new TenantIdentifierWithStorage(null, null, null, storage), + updateSession(new TenantIdentifier(null, null, null), storage, sessionHandle, sessionData, jwtData, version); } - public static void updateSession(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static void updateSession(TenantIdentifier tenantIdentifier, Storage storage, String sessionHandle, @Nullable JsonObject sessionData, @Nullable JsonObject jwtData, AccessToken.VERSION version) throws StorageQueryException, UnauthorisedException, AccessTokenPayloadError { @@ -1010,35 +1014,35 @@ public static void updateSession(TenantIdentifierWithStorage tenantIdentifierWit throw new AccessTokenPayloadError("The user payload contains protected field"); } - io.supertokens.pluginInterface.session.SessionInfo session = tenantIdentifierWithStorage.getSessionStorage() - .getSession(tenantIdentifierWithStorage, sessionHandle); + io.supertokens.pluginInterface.session.SessionInfo session = StorageUtils.getSessionStorage(storage) + .getSession(tenantIdentifier, sessionHandle); // If there is no session, or session is expired if (session == null || session.expiry <= System.currentTimeMillis()) { throw new UnauthorisedException("Session does not exist."); } - int numberOfRowsAffected = tenantIdentifierWithStorage.getSessionStorage() - .updateSession(tenantIdentifierWithStorage, sessionHandle, sessionData, jwtData); + int numberOfRowsAffected = StorageUtils.getSessionStorage(storage) + .updateSession(tenantIdentifier, sessionHandle, sessionData, jwtData); if (numberOfRowsAffected != 1) { throw new UnauthorisedException("Session does not exist."); } } @Deprecated - public static void updateSessionBeforeCDI2_21(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static void updateSessionBeforeCDI2_21(TenantIdentifier tenantIdentifier, Storage storage, String sessionHandle, @Nullable JsonObject sessionData, @Nullable JsonObject jwtData) throws StorageQueryException, UnauthorisedException { - io.supertokens.pluginInterface.session.SessionInfo session = tenantIdentifierWithStorage.getSessionStorage() - .getSession(tenantIdentifierWithStorage, sessionHandle); + io.supertokens.pluginInterface.session.SessionInfo session = StorageUtils.getSessionStorage(storage) + .getSession(tenantIdentifier, sessionHandle); // If there is no session, or session is expired if (session == null || session.expiry <= System.currentTimeMillis()) { throw new UnauthorisedException("Session does not exist."); } - int numberOfRowsAffected = tenantIdentifierWithStorage.getSessionStorage() - .updateSession(tenantIdentifierWithStorage, sessionHandle, sessionData, + int numberOfRowsAffected = StorageUtils.getSessionStorage(storage) + .updateSession(tenantIdentifier, sessionHandle, sessionData, jwtData); if (numberOfRowsAffected != 1) { throw new UnauthorisedException("Session does not exist."); diff --git a/src/main/java/io/supertokens/storageLayer/StorageLayer.java b/src/main/java/io/supertokens/storageLayer/StorageLayer.java index faeaece0d..5145e7c1c 100644 --- a/src/main/java/io/supertokens/storageLayer/StorageLayer.java +++ b/src/main/java/io/supertokens/storageLayer/StorageLayer.java @@ -24,6 +24,7 @@ import io.supertokens.inmemorydb.Start; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.LOG_LEVEL; +import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; @@ -385,7 +386,8 @@ public static List> getTenantsWithUniqueUserPoolId(Main m return result; } - public static Storage[] getStoragesForApp(Main main, AppIdentifier appIdentifier) { + public static Storage[] getStoragesForApp(Main main, AppIdentifier appIdentifier) + throws TenantOrAppNotFoundException { Map userPoolToStorage = new HashMap<>(); Map resources = @@ -397,111 +399,143 @@ public static Storage[] getStoragesForApp(Main main, AppIdentifier appIdentifier userPoolToStorage.put(storage.getUserPoolId(), storage); } } - return userPoolToStorage.values().toArray(new Storage[0]); + Storage[] storages = userPoolToStorage.values().toArray(new Storage[0]); + if (storages.length == 0) { + throw new TenantOrAppNotFoundException(appIdentifier); + } + return storages; } - public static TenantIdentifierWithStorageAndUserIdMapping getTenantIdentifierWithStorageAndUserIdMappingForUser( + public static StorageAndUserIdMapping findStorageAndUserIdMappingForUser( Main main, TenantIdentifier tenantIdentifier, String userId, UserIdType userIdType) throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException { Storage storage = getStorage(tenantIdentifier, main); - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(storage); - UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - tenantIdentifierWithStorage.toAppIdentifierWithStorage(), userId, userIdType); - if (mapping != null) { - return new TenantIdentifierWithStorageAndUserIdMapping(tenantIdentifierWithStorage, mapping); - } + if (userIdType == UserIdType.SUPERTOKENS) { + if (((AuthRecipeStorage) storage).doesUserIdExist(tenantIdentifier.toAppIdentifier(), userId)) { + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + tenantIdentifier.toAppIdentifier(), storage, userId, userIdType); + + return new StorageAndUserIdMapping(storage, mapping); + } + + } else if (userIdType == UserIdType.EXTERNAL) { + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + tenantIdentifier.toAppIdentifier(), storage, + userId, userIdType); + if (mapping != null) { + return new StorageAndUserIdMapping(storage, mapping); + } + } else if (userIdType == UserIdType.ANY) { + if (((AuthRecipeStorage) storage).doesUserIdExist(tenantIdentifier.toAppIdentifier(), userId)) { + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + tenantIdentifier.toAppIdentifier(), storage, userId, userIdType); + + return new StorageAndUserIdMapping(storage, mapping); + } + + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + tenantIdentifier.toAppIdentifier(), storage, + userId, userIdType); + if (mapping != null) { + return new StorageAndUserIdMapping(storage, mapping); + } - if (userIdType != UserIdType.EXTERNAL - && ((AuthRecipeStorage) storage).doesUserIdExist(tenantIdentifier.toAppIdentifier(), userId)) { - return new TenantIdentifierWithStorageAndUserIdMapping( - tenantIdentifierWithStorage, null); - } - if (userIdType != UserIdType.SUPERTOKENS) { try { io.supertokens.useridmapping.UserIdMapping.findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( - tenantIdentifierWithStorage.toAppIdentifierWithStorage(), userId, true); + tenantIdentifier.toAppIdentifier(), storage, userId, true); } catch (ServletException e) { // this means that the userId is being used for a non auth recipe. - return new TenantIdentifierWithStorageAndUserIdMapping( - tenantIdentifierWithStorage, null); + return new StorageAndUserIdMapping( + storage, null); } + + } else { + throw new IllegalStateException("should never come here"); } throw new UnknownUserIdException(); } - public static AppIdentifierWithStorageAndUserIdMapping getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - Main main, AppIdentifier appIdentifier, Storage priorityStorage, String userId, - UserIdType userIdType) throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException { - - Storage[] storages = getStoragesForApp(main, appIdentifier); + public static StorageAndUserIdMapping findStorageAndUserIdMappingForUser( + AppIdentifier appIdentifier, Storage[] storages, String userId, + UserIdType userIdType) throws StorageQueryException, UnknownUserIdException { if (storages.length == 0) { - throw new TenantOrAppNotFoundException(appIdentifier); + throw new IllegalStateException("should never come here"); } - // We look for userId in the priorityStorage first just in case multiple storages have the mapping, we - // return the mapping from the storage of the tenant from which the request came from. - { - UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - appIdentifier.withStorage(priorityStorage), - userId, userIdType); + if (storages[0].getType() != STORAGE_TYPE.SQL) { + // for non sql plugin, there will be only one storage as multitenancy is not supported + assert storages.length == 1; + return new StorageAndUserIdMapping(storages[0], null); + } - if (mapping != null) { - AppIdentifierWithStorage appIdentifierWithStorage = appIdentifier.withStorage(priorityStorage); - return new AppIdentifierWithStorageAndUserIdMapping(appIdentifierWithStorage, mapping); - } + if (userIdType == UserIdType.SUPERTOKENS) { + for (Storage storage : storages) { + if (((AuthRecipeStorage) storage).doesUserIdExist(appIdentifier, userId)) { + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + appIdentifier, storage, + userId, userIdType); - if (userIdType != UserIdType.EXTERNAL - && ((AuthRecipeStorage) priorityStorage).doesUserIdExist(appIdentifier, userId)) { - AppIdentifierWithStorage appIdentifierWithStorage = appIdentifier.withStorage(priorityStorage); - return new AppIdentifierWithStorageAndUserIdMapping(appIdentifierWithStorage, null); - } - if (userIdType != UserIdType.SUPERTOKENS) { - AppIdentifierWithStorage appIdentifierWithStorage = appIdentifier.withStorage(priorityStorage); - try { - io.supertokens.useridmapping.UserIdMapping.findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( - appIdentifierWithStorage, userId, true); - } catch (ServletException e) { - // this means that the userId is being used for a non auth recipe. - return new AppIdentifierWithStorageAndUserIdMapping(appIdentifierWithStorage, null); + return new StorageAndUserIdMapping(storage, mapping); } } - } - for (Storage storage : storages) { - if (storage == priorityStorage) { - continue; // Already checked previously + // Not found in any of the storages + throw new UnknownUserIdException(); + + } else if (userIdType == UserIdType.EXTERNAL) { + for (Storage storage : storages) { + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + appIdentifier, storage, + userId, userIdType); + + if (mapping != null) { + return new StorageAndUserIdMapping(storage, mapping); + } } - UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - appIdentifier.withStorage(storage), - userId, userIdType); + throw new UnknownUserIdException(); + } else if (userIdType == UserIdType.ANY) { - if (mapping != null) { - AppIdentifierWithStorage appIdentifierWithStorage = appIdentifier.withStorage(storage); - return new AppIdentifierWithStorageAndUserIdMapping(appIdentifierWithStorage, mapping); + // look for the user in auth recipes as supertokens user id + for (Storage storage : storages) { + if (((AuthRecipeStorage) storage).doesUserIdExist(appIdentifier, userId)) { + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + appIdentifier, storage, + userId, userIdType); + + return new StorageAndUserIdMapping(storage, mapping); + } } - if (userIdType != UserIdType.EXTERNAL - && ((AuthRecipeStorage) storage).doesUserIdExist(appIdentifier, userId)) { - AppIdentifierWithStorage appIdentifierWithStorage = appIdentifier.withStorage(storage); - return new AppIdentifierWithStorageAndUserIdMapping(appIdentifierWithStorage, null); + // Look for user in auth recipes using user id mapping + for (Storage storage : storages) { + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + appIdentifier, storage, + userId, userIdType); + + if (mapping != null) { + return new StorageAndUserIdMapping(storage, mapping); + } } - if (userIdType != UserIdType.SUPERTOKENS) { - AppIdentifierWithStorage appIdentifierWithStorage = appIdentifier.withStorage(storage); + + // Look for non auth recipes + for (Storage storage : storages) { try { io.supertokens.useridmapping.UserIdMapping.findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( - appIdentifierWithStorage, userId, true); + appIdentifier, storage, userId, true); } catch (ServletException e) { // this means that the userId is being used for a non auth recipe. - return new AppIdentifierWithStorageAndUserIdMapping(appIdentifierWithStorage, null); + return new StorageAndUserIdMapping(storage, null); } } - } - throw new UnknownUserIdException(); + throw new UnknownUserIdException(); + } else { + throw new IllegalStateException("should never come here"); + } } } diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index 69bdcf193..d49f0a93c 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -22,9 +22,11 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; +import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.*; @@ -58,13 +60,13 @@ public SignInUpResponse(boolean createdNewUser, AuthRecipeUserInfo user) { // as seen below. But then, in newer versions, we stopped doing that cause of // https://github.com/supertokens/supertokens-core/issues/295, so we changed the API spec. @Deprecated - public static SignInUpResponse signInUp2_7(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static SignInUpResponse signInUp2_7(TenantIdentifier tenantIdentifier, Storage storage, String thirdPartyId, String thirdPartyUserId, String email, boolean isEmailVerified) throws StorageQueryException, TenantOrAppNotFoundException { SignInUpResponse response = null; try { - response = signInUpHelper(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, + response = signInUpHelper(tenantIdentifier, storage, thirdPartyId, thirdPartyUserId, email); } catch (EmailChangeNotAllowedException e) { throw new RuntimeException(e); @@ -73,16 +75,16 @@ public static SignInUpResponse signInUp2_7(TenantIdentifierWithStorage tenantIde if (isEmailVerified) { try { SignInUpResponse finalResponse = response; - tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { + EmailVerificationSQLStorage evStorage = StorageUtils.getEmailVerificationStorage(storage); + + evStorage.startTransaction(con -> { try { // this assert is there cause this function should only be used for older CDIs in which // account linking was not available. So loginMethod length will always be 1. assert (finalResponse.user.loginMethods.length == 1); - tenantIdentifierWithStorage.getEmailVerificationStorage() - .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + evStorage.updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con, finalResponse.user.getSupertokensUserId(), finalResponse.user.loginMethods[0].email, true); - tenantIdentifierWithStorage.getEmailVerificationStorage() - .commitTransaction(con); + evStorage.commitTransaction(con); return null; } catch (TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(e); @@ -106,7 +108,7 @@ public static SignInUpResponse signInUp2_7(Main main, try { Storage storage = StorageLayer.getStorage(main); return signInUp2_7( - new TenantIdentifierWithStorage(null, null, null, storage), main, + new TenantIdentifier(null, null, null), storage, thirdPartyId, thirdPartyUserId, email, isEmailVerified); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); @@ -119,7 +121,7 @@ public static SignInUpResponse signInUp(Main main, String thirdPartyId, String t try { Storage storage = StorageLayer.getStorage(main); return signInUp( - new TenantIdentifierWithStorage(null, null, null, storage), main, + new TenantIdentifier(null, null, null), storage, main, thirdPartyId, thirdPartyUserId, email, false); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); @@ -132,7 +134,7 @@ public static SignInUpResponse signInUp(Main main, String thirdPartyId, String t try { Storage storage = StorageLayer.getStorage(main); return signInUp( - new TenantIdentifierWithStorage(null, null, null, storage), main, + new TenantIdentifier(null, null, null), storage, main, thirdPartyId, thirdPartyUserId, email, isEmailVerified); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); @@ -140,42 +142,41 @@ public static SignInUpResponse signInUp(Main main, String thirdPartyId, String t } @TestOnly - public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static SignInUpResponse signInUp(TenantIdentifier tenantIdentifier, Storage storage, Main main, String thirdPartyId, String thirdPartyUserId, String email) throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException, EmailChangeNotAllowedException { - return signInUp(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, email, false); + return signInUp(tenantIdentifier, storage, main, thirdPartyId, thirdPartyUserId, email, false); } - public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static SignInUpResponse signInUp(TenantIdentifier tenantIdentifier, Storage storage, Main main, String thirdPartyId, String thirdPartyUserId, String email, boolean isEmailVerified) throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException, EmailChangeNotAllowedException { - TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); + TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifier); if (config == null) { - throw new TenantOrAppNotFoundException(tenantIdentifierWithStorage); + throw new TenantOrAppNotFoundException(tenantIdentifier); } if (!config.thirdPartyConfig.enabled) { throw new BadPermissionException("Third Party login not enabled for tenant"); } - SignInUpResponse response = signInUpHelper(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, + SignInUpResponse response = signInUpHelper(tenantIdentifier, storage, thirdPartyId, thirdPartyUserId, email); if (isEmailVerified) { for (LoginMethod lM : response.user.loginMethods) { if (lM.thirdParty != null && lM.thirdParty.id.equals(thirdPartyId) && lM.thirdParty.userId.equals(thirdPartyUserId)) { try { - tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { + EmailVerificationSQLStorage evStorage = StorageUtils.getEmailVerificationStorage(storage); + evStorage.startTransaction(con -> { try { - tenantIdentifierWithStorage.getEmailVerificationStorage() - .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + evStorage.updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con, lM.getSupertokensUserId(), lM.email, true); - tenantIdentifierWithStorage.getEmailVerificationStorage() - .commitTransaction(con); + evStorage.commitTransaction(con); return null; } catch (TenantOrAppNotFoundException e) { @@ -197,11 +198,11 @@ public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdenti return response; } - private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenantIdentifierWithStorage, - Main main, String thirdPartyId, String thirdPartyUserId, + private static SignInUpResponse signInUpHelper(TenantIdentifier tenantIdentifier, Storage storage, + String thirdPartyId, String thirdPartyUserId, String email) throws StorageQueryException, TenantOrAppNotFoundException, EmailChangeNotAllowedException { - ThirdPartySQLStorage storage = tenantIdentifierWithStorage.getThirdPartyStorage(); + ThirdPartySQLStorage tpStorage = StorageUtils.getThirdPartyStorage(storage); while (true) { // loop for sign in + sign up @@ -211,7 +212,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan long timeJoined = System.currentTimeMillis(); try { - AuthRecipeUserInfo createdUser = storage.signUp(tenantIdentifierWithStorage, userId, email, + AuthRecipeUserInfo createdUser = tpStorage.signUp(tenantIdentifier, userId, email, new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId), timeJoined); return new SignInUpResponse(true, createdUser); @@ -224,9 +225,8 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } // we try to get user and update their email - AppIdentifier appIdentifier = tenantIdentifierWithStorage.toAppIdentifier(); - AuthRecipeSQLStorage authRecipeStorage = - (AuthRecipeSQLStorage) tenantIdentifierWithStorage.getAuthRecipeStorage(); + AppIdentifier appIdentifier = tenantIdentifier.toAppIdentifier(); + AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage); { // Try without transaction, because in most cases we might not need to update the email AuthRecipeUserInfo userFromDb = null; @@ -235,7 +235,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan appIdentifier, thirdPartyId, thirdPartyUserId); for (AuthRecipeUserInfo user : usersFromDb) { - if (user.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + if (user.tenantIds.contains(tenantIdentifier.getTenantId())) { if (userFromDb != null) { throw new IllegalStateException("Should never happen"); } @@ -265,7 +265,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan // Email needs updating, so repeat everything in a transaction try { - storage.startTransaction(con -> { + tpStorage.startTransaction(con -> { AuthRecipeUserInfo userFromDb1 = null; AuthRecipeUserInfo[] usersFromDb1 = authRecipeStorage.listPrimaryUsersByThirdPartyInfo_Transaction( @@ -273,7 +273,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan con, thirdPartyId, thirdPartyUserId); for (AuthRecipeUserInfo user : usersFromDb1) { - if (user.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + if (user.tenantIds.contains(tenantIdentifier.getTenantId())) { if (userFromDb1 != null) { throw new IllegalStateException("Should never happen"); } @@ -282,7 +282,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } if (userFromDb1 == null) { - storage.commitTransaction(con); + tpStorage.commitTransaction(con); return null; } @@ -320,11 +320,11 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } } } - storage.updateUserEmail_Transaction(appIdentifier, con, + tpStorage.updateUserEmail_Transaction(appIdentifier, con, thirdPartyId, thirdPartyUserId, email); } - storage.commitTransaction(con); + tpStorage.commitTransaction(con); return null; }); } catch (StorageTransactionLogicException e) { @@ -336,16 +336,16 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } } - AuthRecipeUserInfo user = getUser(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); + AuthRecipeUserInfo user = getUser(tenantIdentifier, storage, thirdPartyId, thirdPartyUserId); return new SignInUpResponse(false, user); } } @Deprecated - public static AuthRecipeUserInfo getUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static AuthRecipeUserInfo getUser(AppIdentifier appIdentifier, Storage storage, String userId) throws StorageQueryException { - AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() - .getPrimaryUserById(appIdentifierWithStorage, userId); + AuthRecipeUserInfo result = StorageUtils.getAuthRecipeStorage(storage) + .getPrimaryUserById(appIdentifier, userId); if (result == null) { return null; } @@ -362,15 +362,15 @@ public static AuthRecipeUserInfo getUser(AppIdentifierWithStorage appIdentifierW @TestOnly public static AuthRecipeUserInfo getUser(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return getUser(new AppIdentifierWithStorage(null, null, storage), userId); + return getUser(new AppIdentifier(null, null), storage, userId); } - public static AuthRecipeUserInfo getUser(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static AuthRecipeUserInfo getUser(TenantIdentifier tenantIdentifier, Storage storage, String thirdPartyId, String thirdPartyUserId) throws StorageQueryException { - return tenantIdentifierWithStorage.getThirdPartyStorage() - .getPrimaryUserByThirdPartyInfo(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); + return StorageUtils.getThirdPartyStorage(storage) + .getPrimaryUserByThirdPartyInfo(tenantIdentifier, thirdPartyId, thirdPartyUserId); } @TestOnly @@ -378,16 +378,16 @@ public static AuthRecipeUserInfo getUser(Main main, String thirdPartyId, String throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUser( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, thirdPartyId, thirdPartyUserId); } @Deprecated - public static AuthRecipeUserInfo[] getUsersByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, + public static AuthRecipeUserInfo[] getUsersByEmail(TenantIdentifier tenantIdentifier, Storage storage, @Nonnull String email) throws StorageQueryException { - AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getThirdPartyStorage() - .listPrimaryUsersByEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] users = StorageUtils.getThirdPartyStorage(storage) + .listPrimaryUsersByEmail(tenantIdentifier, email); List result = new ArrayList<>(); for (AuthRecipeUserInfo user : users) { for (LoginMethod lM : user.loginMethods) { diff --git a/src/main/java/io/supertokens/totp/Totp.java b/src/main/java/io/supertokens/totp/Totp.java index a03a38fda..e76dd6182 100644 --- a/src/main/java/io/supertokens/totp/Totp.java +++ b/src/main/java/io/supertokens/totp/Totp.java @@ -7,8 +7,10 @@ import io.supertokens.mfa.Mfa; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.totp.TOTPDevice; import io.supertokens.pluginInterface.totp.TOTPUsedCode; @@ -72,36 +74,37 @@ private static boolean checkCode(TOTPDevice device, String code) { public static TOTPDevice registerDevice(Main main, String userId, String deviceName, int skew, int period) throws StorageQueryException, DeviceAlreadyExistsException, NoSuchAlgorithmException, - FeatureNotEnabledException, StorageTransactionLogicException { + FeatureNotEnabledException { try { - return registerDevice(new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(main)), main, userId, - deviceName, skew, period); + return registerDevice(new AppIdentifier(null, null), StorageLayer.getStorage(main), + main, userId, deviceName, skew, period); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static TOTPDevice createDevice(Main main, AppIdentifierWithStorage appIdentifierWithStorage, String userId, + public static TOTPDevice createDevice(Main main, AppIdentifier appIdentifier, Storage storage, String userId, String deviceName, int skew, int period, String secretKey, boolean verified, long createdAt) throws DeviceAlreadyExistsException, StorageQueryException, FeatureNotEnabledException, TenantOrAppNotFoundException { - Mfa.checkForMFAFeature(appIdentifierWithStorage, main); + Mfa.checkForMFAFeature(appIdentifier, main); if (deviceName != null) { - TOTPSQLStorage totpStorage = appIdentifierWithStorage.getTOTPStorage(); + TOTPSQLStorage totpStorage = StorageUtils.getTOTPStorage(storage); try { return totpStorage.startTransaction(con -> { try { - TOTPDevice existingDevice = totpStorage.getDeviceByName_Transaction(con, appIdentifierWithStorage, userId, deviceName); + TOTPDevice existingDevice = totpStorage.getDeviceByName_Transaction(con, appIdentifier, userId, + deviceName); if (existingDevice == null) { - return totpStorage.createDevice_Transaction(con, appIdentifierWithStorage, new TOTPDevice( + return totpStorage.createDevice_Transaction(con, appIdentifier, new TOTPDevice( userId, deviceName, secretKey, period, skew, verified, createdAt )); } else if (!existingDevice.verified) { - totpStorage.deleteDevice_Transaction(con, appIdentifierWithStorage, userId, deviceName); - return totpStorage.createDevice_Transaction(con, appIdentifierWithStorage, new TOTPDevice( + totpStorage.deleteDevice_Transaction(con, appIdentifier, userId, deviceName); + return totpStorage.createDevice_Transaction(con, appIdentifier, new TOTPDevice( userId, deviceName, secretKey, period, skew, verified, createdAt )); } else { @@ -119,13 +122,13 @@ public static TOTPDevice createDevice(Main main, AppIdentifierWithStorage appIde } } - TOTPSQLStorage totpStorage = appIdentifierWithStorage.getTOTPStorage(); - TOTPDevice[] devices = totpStorage.getDevices(appIdentifierWithStorage, userId); + TOTPSQLStorage totpStorage = StorageUtils.getTOTPStorage(storage); + TOTPDevice[] devices = totpStorage.getDevices(appIdentifier, userId); int verifiedDevicesCount = Arrays.stream(devices).filter(d -> d.verified).toArray().length; while (true) { try { - return createDevice(main, appIdentifierWithStorage, + return createDevice(main, appIdentifier, storage, userId, "TOTP Device " + verifiedDevicesCount, skew, @@ -140,18 +143,18 @@ public static TOTPDevice createDevice(Main main, AppIdentifierWithStorage appIde } } - public static TOTPDevice registerDevice(AppIdentifierWithStorage appIdentifierWithStorage, Main main, String userId, + public static TOTPDevice registerDevice(AppIdentifier appIdentifier, Storage storage, Main main, String userId, String deviceName, int skew, int period) throws StorageQueryException, DeviceAlreadyExistsException, NoSuchAlgorithmException, - FeatureNotEnabledException, TenantOrAppNotFoundException, StorageTransactionLogicException { + FeatureNotEnabledException, TenantOrAppNotFoundException { String secretKey = generateSecret(); - return createDevice(main, appIdentifierWithStorage, userId, deviceName, skew, period, secretKey, false, + return createDevice(main, appIdentifier, storage, userId, deviceName, skew, period, secretKey, false, System.currentTimeMillis()); } - private static void checkAndStoreCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + private static void checkAndStoreCode(TenantIdentifier tenantIdentifier, Storage storage, Main main, String userId, TOTPDevice[] devices, String code) throws InvalidTotpException, UnknownTotpUserIdException, @@ -190,21 +193,20 @@ private static void checkAndStoreCode(TenantIdentifierWithStorage tenantIdentifi // That's why we need to fetch all the codes (expired + non-expired). // TOTPUsedCode[] usedCodes = - TOTPSQLStorage totpSQLStorage = tenantIdentifierWithStorage.getTOTPStorage(); + TOTPSQLStorage totpSQLStorage = StorageUtils.getTOTPStorage(storage); try { totpSQLStorage.startTransaction(con -> { try { TOTPUsedCode[] usedCodes = totpSQLStorage.getAllUsedCodesDescOrder_Transaction(con, - tenantIdentifierWithStorage, - userId); + tenantIdentifier, userId); // N represents # of invalid attempts that will trigger rate limiting: - int N = Config.getConfig(tenantIdentifierWithStorage, main).getTotpMaxAttempts(); // (Default 5) + 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(); - int rateLimitResetTimeInMs = Config.getConfig(tenantIdentifierWithStorage, main) + int rateLimitResetTimeInMs = Config.getConfig(tenantIdentifier, main) .getTotpRateLimitCooldownTimeSec() * 1000; // (Default 15 mins) @@ -273,7 +275,7 @@ private static void checkAndStoreCode(TenantIdentifierWithStorage tenantIdentifi code, isValid, now + 1000L * expireInSec, now); try { - totpSQLStorage.insertUsedCode_Transaction(con, tenantIdentifierWithStorage, newCode); + totpSQLStorage.insertUsedCode_Transaction(con, tenantIdentifier, newCode); totpSQLStorage.commitTransaction(con); } catch (UnknownTotpUserIdException e) { throw new StorageTransactionLogicException(e); @@ -314,14 +316,14 @@ public static boolean verifyDevice(Main main, throws UnknownDeviceException, InvalidTotpException, LimitReachedException, StorageQueryException, StorageTransactionLogicException { try { - return verifyDevice(new TenantIdentifierWithStorage(null, null, null, StorageLayer.getStorage(main)), main, - userId, deviceName, code); + return verifyDevice(new TenantIdentifier(null, null, null), + StorageLayer.getStorage(main), main, userId, deviceName, code); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static boolean verifyDevice(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static boolean verifyDevice(TenantIdentifier tenantIdentifier, Storage storage, Main main, String userId, String deviceName, String code) throws UnknownDeviceException, InvalidTotpException, LimitReachedException, StorageQueryException, StorageTransactionLogicException, @@ -329,7 +331,7 @@ public static boolean verifyDevice(TenantIdentifierWithStorage tenantIdentifierW // Here boolean return value tells whether the device has been // newly verified (true) OR it was already verified (false) - TOTPSQLStorage totpStorage = tenantIdentifierWithStorage.getTOTPStorage(); + TOTPSQLStorage totpStorage = StorageUtils.getTOTPStorage(storage); TOTPDevice matchingDevice = null; // Here one race condition is that the same device @@ -337,7 +339,7 @@ public static boolean verifyDevice(TenantIdentifierWithStorage tenantIdentifierW // both the API calls will return true, but that's okay. // Check if the user has any devices: - TOTPDevice[] devices = totpStorage.getDevices(tenantIdentifierWithStorage.toAppIdentifier(), userId); + TOTPDevice[] devices = totpStorage.getDevices(tenantIdentifier.toAppIdentifier(), userId); if (devices.length == 0) { throw new UnknownDeviceException(); } @@ -365,13 +367,13 @@ public static boolean verifyDevice(TenantIdentifierWithStorage tenantIdentifierW // gets a UnknownDevceException. // This behaviour is okay so we can ignore it. try { - checkAndStoreCode(tenantIdentifierWithStorage, main, userId, new TOTPDevice[] { matchingDevice }, code); + checkAndStoreCode(tenantIdentifier, storage, main, userId, new TOTPDevice[] { matchingDevice }, code); } catch (UnknownTotpUserIdException e) { // User must have deleted the device in parallel. throw new UnknownDeviceException(); } // Will reach here only if the code is valid: - totpStorage.markDeviceAsVerified(tenantIdentifierWithStorage.toAppIdentifier(), userId, deviceName); + totpStorage.markDeviceAsVerified(tenantIdentifier.toAppIdentifier(), userId, deviceName); return true; // Newly verified } @@ -380,24 +382,24 @@ public static void verifyCode(Main main, String userId, String code) throws InvalidTotpException, UnknownTotpUserIdException, LimitReachedException, StorageQueryException, StorageTransactionLogicException, FeatureNotEnabledException { try { - verifyCode(new TenantIdentifierWithStorage(null, null, null, StorageLayer.getStorage(main)), main, + verifyCode(new TenantIdentifier(null, null, null), StorageLayer.getStorage(main), main, userId, code); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static void verifyCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String userId, String code) + public static void verifyCode(TenantIdentifier tenantIdentifier, Storage storage, Main main, String userId, String code) throws InvalidTotpException, UnknownTotpUserIdException, LimitReachedException, StorageQueryException, StorageTransactionLogicException, FeatureNotEnabledException, TenantOrAppNotFoundException { - Mfa.checkForMFAFeature(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main); + Mfa.checkForMFAFeature(tenantIdentifier.toAppIdentifier(), main); - TOTPSQLStorage totpStorage = tenantIdentifierWithStorage.getTOTPStorage(); + TOTPSQLStorage totpStorage = StorageUtils.getTOTPStorage(storage); // Check if the user has any devices: - TOTPDevice[] devices = totpStorage.getDevices(tenantIdentifierWithStorage.toAppIdentifier(), userId); + TOTPDevice[] devices = totpStorage.getDevices(tenantIdentifier.toAppIdentifier(), userId); if (devices.length == 0) { // No devices found. So we can't verify the code anyway. throw new UnknownTotpUserIdException(); @@ -414,7 +416,7 @@ public static void verifyCode(TenantIdentifierWithStorage tenantIdentifierWithSt // UnknownTotpUserIdException will be thrown when // the User has deleted the device in parallel // since they cannot un-verify a device (no API exists) - checkAndStoreCode(tenantIdentifierWithStorage, main, userId, devices, code); + checkAndStoreCode(tenantIdentifier, storage, main, userId, devices, code); } @TestOnly @@ -423,7 +425,7 @@ public static void removeDevice(Main main, String userId, throws StorageQueryException, UnknownDeviceException, StorageTransactionLogicException { try { - removeDevice(new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(main)), + removeDevice(new AppIdentifier(null, null), StorageLayer.getStorage(main), userId, deviceName); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); @@ -433,28 +435,28 @@ public static void removeDevice(Main main, String userId, /** * Delete device and also delete the user if deleting the last device */ - public static void removeDevice(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + public static void removeDevice(AppIdentifier appIdentifier, Storage storage, String userId, String deviceName) throws StorageQueryException, UnknownDeviceException, StorageTransactionLogicException, TenantOrAppNotFoundException { - TOTPSQLStorage storage = appIdentifierWithStorage.getTOTPStorage(); + TOTPSQLStorage totpStorage = StorageUtils.getTOTPStorage(storage); try { - storage.startTransaction(con -> { - int deletedCount = storage.deleteDevice_Transaction(con, appIdentifierWithStorage, userId, deviceName); + totpStorage.startTransaction(con -> { + int deletedCount = totpStorage.deleteDevice_Transaction(con, appIdentifier, userId, deviceName); if (deletedCount == 0) { throw new StorageTransactionLogicException(new UnknownDeviceException()); } // Some device(s) were deleted. Check if user has any other device left: // This also takes a lock on the user devices. - TOTPDevice[] devices = storage.getDevices_Transaction(con, appIdentifierWithStorage, userId); + TOTPDevice[] devices = totpStorage.getDevices_Transaction(con, appIdentifier, userId); if (devices.length == 0) { // no device left. delete user - storage.removeUser_Transaction(con, appIdentifierWithStorage, userId); + totpStorage.removeUser_Transaction(con, appIdentifier, userId); } - storage.commitTransaction(con); + totpStorage.commitTransaction(con); return null; }); return; @@ -472,38 +474,37 @@ public static void updateDeviceName(Main main, String userId, String oldDeviceName, String newDeviceName) throws StorageQueryException, DeviceAlreadyExistsException, UnknownDeviceException { try { - updateDeviceName(new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(main)), + updateDeviceName(new AppIdentifier(null, null), StorageLayer.getStorage(main), userId, oldDeviceName, newDeviceName); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static void updateDeviceName(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + public static void updateDeviceName(AppIdentifier appIdentifier, Storage storage, String userId, String oldDeviceName, String newDeviceName) throws StorageQueryException, DeviceAlreadyExistsException, UnknownDeviceException, TenantOrAppNotFoundException { - TOTPSQLStorage totpStorage = appIdentifierWithStorage.getTOTPStorage(); - totpStorage.updateDeviceName(appIdentifierWithStorage, userId, oldDeviceName, newDeviceName); + TOTPSQLStorage totpStorage = StorageUtils.getTOTPStorage(storage); + totpStorage.updateDeviceName(appIdentifier, userId, oldDeviceName, newDeviceName); } @TestOnly public static TOTPDevice[] getDevices(Main main, String userId) throws StorageQueryException { try { - return getDevices(new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(main)), + return getDevices(new AppIdentifier(null, null), StorageLayer.getStorage(main), userId); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static TOTPDevice[] getDevices(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static TOTPDevice[] getDevices(AppIdentifier appIdentifier, Storage storage, String userId) throws StorageQueryException, TenantOrAppNotFoundException { - TOTPSQLStorage totpStorage = appIdentifierWithStorage.getTOTPStorage(); + TOTPSQLStorage totpStorage = StorageUtils.getTOTPStorage(storage); - TOTPDevice[] devices = totpStorage.getDevices(appIdentifierWithStorage, userId); + TOTPDevice[] devices = totpStorage.getDevices(appIdentifier, userId); return devices; } - } diff --git a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java index 56a220152..274e08efc 100644 --- a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java +++ b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java @@ -16,10 +16,10 @@ package io.supertokens.useridmapping; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.pluginInterface.Storage; -import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; @@ -27,8 +27,7 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.jwt.JWTRecipeStorage; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.session.SessionStorage; import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; @@ -50,16 +49,26 @@ public class UserIdMapping { @TestOnly - public static void createUserIdMapping(Main main, AppIdentifierWithStorage appIdentifierWithStorage, + public static void createUserIdMapping(AppIdentifier appIdentifier, Storage[] storages, String superTokensUserId, String externalUserId, String externalUserIdInfo, boolean force) throws ServletException, UnknownSuperTokensUserIdException, UserIdMappingAlreadyExistsException, StorageQueryException, TenantOrAppNotFoundException { - createUserIdMapping(main, appIdentifierWithStorage, superTokensUserId, externalUserId, externalUserIdInfo, + createUserIdMapping(appIdentifier, storages, superTokensUserId, externalUserId, externalUserIdInfo, force, false); } - public static void createUserIdMapping(Main main, AppIdentifierWithStorage appIdentifierWithStorage, + @TestOnly + public static void createUserIdMapping(Main main, AppIdentifier appIdentifier, Storage storage, String supertokensUserId, String externalUserId, String externalUserIdInfo, boolean force) + throws ServletException, UnknownSuperTokensUserIdException, UserIdMappingAlreadyExistsException, + StorageQueryException, TenantOrAppNotFoundException { + createUserIdMapping( + new AppIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId()), + new Storage[]{storage}, supertokensUserId, externalUserId, externalUserIdInfo, force + ); + } + + public static void createUserIdMapping(AppIdentifier appIdentifier, Storage[] storages, String superTokensUserId, String externalUserId, String externalUserIdInfo, boolean force, boolean makeExceptionForEmailVerification) throws UnknownSuperTokensUserIdException, @@ -74,10 +83,9 @@ public static void createUserIdMapping(Main main, AppIdentifierWithStorage appId // This issue - https://github.com/supertokens/supertokens-core/issues/610 - must be resolved when the // race condition is fixed. try { // with external id - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - main, appIdentifierWithStorage, appIdentifierWithStorage.getStorage(), externalUserId, - UserIdType.EXTERNAL); + StorageAndUserIdMapping mappingAndStorage = + StorageLayer.findStorageAndUserIdMappingForUser( + appIdentifier, storages, externalUserId, UserIdType.EXTERNAL); if (mappingAndStorage.userIdMapping != null) { throw new UserIdMappingAlreadyExistsException( @@ -89,6 +97,16 @@ public static void createUserIdMapping(Main main, AppIdentifierWithStorage appId // ignore this as we do not want external user id to exist } + StorageAndUserIdMapping mappingAndStorage; + try { + mappingAndStorage = StorageLayer.findStorageAndUserIdMappingForUser( + appIdentifier, storages, superTokensUserId, UserIdType.SUPERTOKENS); + } catch (UnknownUserIdException e) { + throw new UnknownSuperTokensUserIdException(); + } + + Storage userStorage = mappingAndStorage.storage; + // if a userIdMapping is created with force, then we skip the following checks if (!force) { // We do not allow for a UserIdMapping to be created when the externalUserId is a SuperTokens userId. @@ -98,8 +116,8 @@ public static void createUserIdMapping(Main main, AppIdentifierWithStorage appId // ignore it. { - if (((AuthRecipeStorage) appIdentifierWithStorage.getStorage()).doesUserIdExist( - appIdentifierWithStorage, externalUserId)) { + if (StorageUtils.getAuthRecipeStorage(userStorage).doesUserIdExist( + appIdentifier, externalUserId)) { throw new ServletException(new WebserverAPI.BadRequestException( "Cannot create a userId mapping where the externalId is also a SuperTokens userID")); } @@ -107,7 +125,8 @@ public static void createUserIdMapping(Main main, AppIdentifierWithStorage appId if (makeExceptionForEmailVerification) { // check that none of the non-auth recipes are using the superTokensUserId - List storageClasses = findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed(appIdentifierWithStorage, superTokensUserId, false); + List storageClasses = findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed(appIdentifier, + userStorage, superTokensUserId, false); if (storageClasses.size() == 1 && storageClasses.get(0).equals(EmailVerificationStorage.class.getName())) { // if the userId is used in email verification, then we do an exception and update the isEmailVerified // to the externalUserId. We do this because we automatically set the isEmailVerified to true for passwordless @@ -115,7 +134,8 @@ public static void createUserIdMapping(Main main, AppIdentifierWithStorage appId // an exception, then the creation of userIdMapping for the user will be blocked. And, to overcome that the // email will have to be unverified first, then the userIdMapping should be created and then the email must be // verified again on the externalUserId, which is not a good user experience. - appIdentifierWithStorage.getEmailVerificationStorage().updateIsEmailVerifiedToExternalUserId(appIdentifierWithStorage, superTokensUserId, externalUserId); + StorageUtils.getEmailVerificationStorage(userStorage).updateIsEmailVerifiedToExternalUserId( + appIdentifier, superTokensUserId, externalUserId); } else if (storageClasses.size() > 0) { String recipeName = storageClasses.get(0); String[] parts = recipeName.split("[.]"); @@ -125,16 +145,15 @@ public static void createUserIdMapping(Main main, AppIdentifierWithStorage appId "UserId is already in use in " + recipeName + " recipe")); } } else { - findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed(appIdentifierWithStorage, superTokensUserId, true); + findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed(appIdentifier, userStorage, superTokensUserId, true); } - - } - appIdentifierWithStorage.getUserIdMappingStorage() - .createUserIdMapping(appIdentifierWithStorage, superTokensUserId, + StorageUtils.getUserIdMappingStorage(userStorage) + .createUserIdMapping(appIdentifier, superTokensUserId, externalUserId, externalUserIdInfo); } + @TestOnly public static void createUserIdMapping(Main main, String superTokensUserId, String externalUserId, @@ -152,23 +171,23 @@ public static void createUserIdMapping(Main main, UserIdMappingAlreadyExistsException, StorageQueryException, ServletException, UnknownUserIdException { try { Storage storage = StorageLayer.getStorage(main); - createUserIdMapping(main, new AppIdentifierWithStorage(null, null, storage), superTokensUserId, - externalUserId, - externalUserIdInfo, force, makeExceptionForEmailVerification); + createUserIdMapping(new AppIdentifier(null, null), new Storage[]{storage}, superTokensUserId, + externalUserId, externalUserIdInfo, force, makeExceptionForEmailVerification); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } public static io.supertokens.pluginInterface.useridmapping.UserIdMapping getUserIdMapping( - AppIdentifierWithStorage appIdentifierWithStorage, String userId, + AppIdentifier appIdentifier, Storage storage, String userId, UserIdType userIdType) throws StorageQueryException { - UserIdMappingSQLStorage storage = (UserIdMappingSQLStorage) appIdentifierWithStorage.getUserIdMappingStorage(); + UserIdMappingSQLStorage uidMappingStorage = + (UserIdMappingSQLStorage) storage; try { - return storage.startTransaction(con -> { - return getUserIdMapping(con, appIdentifierWithStorage, userId, userIdType); + return uidMappingStorage.startTransaction(con -> { + return getUserIdMapping(con, appIdentifier, uidMappingStorage, userId, userIdType); }); } catch (StorageTransactionLogicException e) { if (e.actualException instanceof StorageQueryException) { @@ -181,21 +200,22 @@ public static io.supertokens.pluginInterface.useridmapping.UserIdMapping getUser public static io.supertokens.pluginInterface.useridmapping.UserIdMapping getUserIdMapping( TransactionConnection con, - AppIdentifierWithStorage appIdentifierWithStorage, String userId, + AppIdentifier appIdentifier, Storage storage, String userId, UserIdType userIdType) throws StorageQueryException { - UserIdMappingSQLStorage storage = (UserIdMappingSQLStorage) appIdentifierWithStorage.getUserIdMappingStorage(); + UserIdMappingSQLStorage uidMappingStorage = + (UserIdMappingSQLStorage) storage; if (userIdType == UserIdType.SUPERTOKENS) { - return storage.getUserIdMapping_Transaction(con, appIdentifierWithStorage, userId, true); + return uidMappingStorage.getUserIdMapping_Transaction(con, appIdentifier, userId, true); } if (userIdType == UserIdType.EXTERNAL) { - return storage.getUserIdMapping_Transaction(con, appIdentifierWithStorage, userId, false); + return uidMappingStorage.getUserIdMapping_Transaction(con, appIdentifier, userId, false); } - io.supertokens.pluginInterface.useridmapping.UserIdMapping[] userIdMappings = storage.getUserIdMapping_Transaction( - con, appIdentifierWithStorage, userId); + io.supertokens.pluginInterface.useridmapping.UserIdMapping[] userIdMappings = uidMappingStorage.getUserIdMapping_Transaction( + con, appIdentifier, userId); if (userIdMappings.length == 0) { return null; @@ -222,25 +242,25 @@ public static io.supertokens.pluginInterface.useridmapping.UserIdMapping getUser UserIdType userIdType) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return getUserIdMapping(new AppIdentifierWithStorage(null, null, storage), userId, userIdType); + return getUserIdMapping(new AppIdentifier(null, null), storage, userId, userIdType); } - public static boolean deleteUserIdMapping(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + public static boolean deleteUserIdMapping(AppIdentifier appIdentifier, Storage storage, String userId, UserIdType userIdType, boolean force) throws StorageQueryException, ServletException { // referring to // https://docs.google.com/spreadsheets/d/17hYV32B0aDCeLnSxbZhfRN2Y9b0LC2xUF44vV88RNAA/edit?usp=sharing // we need to check if db is in A3 or A4. - io.supertokens.pluginInterface.useridmapping.UserIdMapping mapping = getUserIdMapping(appIdentifierWithStorage, - userId, UserIdType.ANY); - UserIdMappingStorage storage = appIdentifierWithStorage.getUserIdMappingStorage(); + io.supertokens.pluginInterface.useridmapping.UserIdMapping mapping = getUserIdMapping(appIdentifier, + storage, userId, UserIdType.ANY); + UserIdMappingStorage uidMappingStorage = StorageUtils.getUserIdMappingStorage(storage); if (mapping != null) { - if (((AuthRecipeStorage) appIdentifierWithStorage.getStorage()).doesUserIdExist( - appIdentifierWithStorage, mapping.externalUserId)) { + if (StorageUtils.getAuthRecipeStorage(storage).doesUserIdExist( + appIdentifier, mapping.externalUserId)) { // this means that the db is in state A4 - return storage.deleteUserIdMapping(appIdentifierWithStorage, mapping.superTokensUserId, true); + return uidMappingStorage.deleteUserIdMapping(appIdentifier, mapping.superTokensUserId, true); } } else { return false; @@ -251,23 +271,23 @@ public static boolean deleteUserIdMapping(AppIdentifierWithStorage appIdentifier String externalId = mapping.externalUserId; // check if externalId is used in any non-auth recipes - findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed(appIdentifierWithStorage, externalId, true); + findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed(appIdentifier, storage, externalId, true); } // db is in state A3 if (userIdType == UserIdType.SUPERTOKENS) { - return storage.deleteUserIdMapping(appIdentifierWithStorage, userId, true); + return uidMappingStorage.deleteUserIdMapping(appIdentifier, userId, true); } if (userIdType == UserIdType.EXTERNAL) { - return storage.deleteUserIdMapping(appIdentifierWithStorage, userId, false); + return uidMappingStorage.deleteUserIdMapping(appIdentifier, userId, false); } - if (((AuthRecipeStorage) appIdentifierWithStorage.getStorage()).doesUserIdExist(appIdentifierWithStorage, + if (StorageUtils.getAuthRecipeStorage(storage).doesUserIdExist(appIdentifier, userId)) { - return storage.deleteUserIdMapping(appIdentifierWithStorage, userId, true); + return uidMappingStorage.deleteUserIdMapping(appIdentifier, userId, true); } - return storage.deleteUserIdMapping(appIdentifierWithStorage, userId, false); + return uidMappingStorage.deleteUserIdMapping(appIdentifier, userId, false); } @TestOnly @@ -276,34 +296,34 @@ public static boolean deleteUserIdMapping(Main main, String userId, throws StorageQueryException, ServletException { Storage storage = StorageLayer.getStorage(main); return deleteUserIdMapping( - new AppIdentifierWithStorage(null, null, storage), userId, userIdType, force); + new AppIdentifier(null, null), storage, userId, userIdType, force); } - public static boolean updateOrDeleteExternalUserIdInfo(AppIdentifierWithStorage appIdentifierWithStorage, + public static boolean updateOrDeleteExternalUserIdInfo(AppIdentifier appIdentifier, Storage storage, String userId, UserIdType userIdType, @Nullable String externalUserIdInfo) throws StorageQueryException { - UserIdMappingStorage storage = appIdentifierWithStorage.getUserIdMappingStorage(); + UserIdMappingStorage uidMappingStorage = StorageUtils.getUserIdMappingStorage(storage); if (userIdType == UserIdType.SUPERTOKENS) { - return storage.updateOrDeleteExternalUserIdInfo(appIdentifierWithStorage, userId, true, + return uidMappingStorage.updateOrDeleteExternalUserIdInfo(appIdentifier, userId, true, externalUserIdInfo); } if (userIdType == UserIdType.EXTERNAL) { - return storage.updateOrDeleteExternalUserIdInfo(appIdentifierWithStorage, userId, false, + return uidMappingStorage.updateOrDeleteExternalUserIdInfo(appIdentifier, userId, false, externalUserIdInfo); } // userIdType == UserIdType.ANY // if userId exists in authRecipeStorage, it means it is a UserIdType.SUPERTOKENS - if (((AuthRecipeStorage) appIdentifierWithStorage.getStorage()).doesUserIdExist(appIdentifierWithStorage, + if (StorageUtils.getAuthRecipeStorage(storage).doesUserIdExist(appIdentifier, userId)) { - return storage.updateOrDeleteExternalUserIdInfo(appIdentifierWithStorage, userId, true, + return uidMappingStorage.updateOrDeleteExternalUserIdInfo(appIdentifier, userId, true, externalUserIdInfo); } // else treat it as UserIdType.EXTERNAL - return storage.updateOrDeleteExternalUserIdInfo(appIdentifierWithStorage, userId, false, + return uidMappingStorage.updateOrDeleteExternalUserIdInfo(appIdentifier, userId, false, externalUserIdInfo); } @@ -313,25 +333,16 @@ public static boolean updateOrDeleteExternalUserIdInfo(Main main, @Nullable String externalUserIdInfo) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return updateOrDeleteExternalUserIdInfo(new AppIdentifierWithStorage( - null, null, storage), + return updateOrDeleteExternalUserIdInfo(new AppIdentifier(null, null), storage, userId, userIdType, externalUserIdInfo); } public static HashMap getUserIdMappingForSuperTokensUserIds( - TenantIdentifierWithStorage tenantIdentifierWithStorage, - ArrayList userIds) - throws StorageQueryException { - // userIds are already filtered for a tenant, so this becomes a tenant specific operation. - return tenantIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds(userIds); - } - - public static HashMap getUserIdMappingForSuperTokensUserIds( - AppIdentifierWithStorage appIdentifierWithStorage, + Storage storage, ArrayList userIds) throws StorageQueryException { - // userIds are already filtered for a tenant, so this becomes a tenant specific operation. - return appIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds(userIds); + // userIds are already filtered for a tenant + return StorageUtils.getUserIdMappingStorage(storage).getUserIdMappingForSuperTokensIds(userIds); } @TestOnly @@ -339,18 +350,16 @@ public static HashMap getUserIdMappingForSuperTokensUserIds(Main ArrayList userIds) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return getUserIdMappingForSuperTokensUserIds( - new TenantIdentifierWithStorage(null, null, null, storage), userIds); + return getUserIdMappingForSuperTokensUserIds(storage, userIds); } public static List findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( - AppIdentifierWithStorage appIdentifierWithStorage, String userId, boolean assertIfUsed) + AppIdentifier appIdentifier, Storage storage, String userId, boolean assertIfUsed) throws StorageQueryException, ServletException { - Storage storage = appIdentifierWithStorage.getStorage(); List result = new ArrayList<>(); { - if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifierWithStorage, + if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifier, SessionStorage.class.getName(), userId)) { result.add(SessionStorage.class.getName()); @@ -361,7 +370,7 @@ public static List findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( } } { - if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifierWithStorage, + if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifier, UserMetadataStorage.class.getName(), userId)) { result.add(UserMetadataStorage.class.getName()); @@ -372,7 +381,7 @@ public static List findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( } } { - if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifierWithStorage, + if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifier, UserRolesStorage.class.getName(), userId)) { result.add(UserRolesStorage.class.getName()); @@ -383,7 +392,7 @@ public static List findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( } } { - if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifierWithStorage, + if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifier, EmailVerificationStorage.class.getName(), userId)) { result.add(EmailVerificationStorage.class.getName()); @@ -394,14 +403,14 @@ public static List findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( } } { - if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifierWithStorage, + if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifier, JWTRecipeStorage.class.getName(), userId)) { throw new ServletException(new WebserverAPI.BadRequestException("Should never come here")); } } { - if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifierWithStorage, TOTPStorage.class.getName(), + if (storage.isUserIdBeingUsedInNonAuthRecipe(appIdentifier, TOTPStorage.class.getName(), userId)) { result.add(TOTPStorage.class.getName()); if (assertIfUsed) { @@ -413,32 +422,7 @@ public static List findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( return result; } - public static void populateExternalUserIdForUsers(AppIdentifierWithStorage appIdentifierWithStorage, AuthRecipeUserInfo[] users) - throws StorageQueryException { - Set userIds = new HashSet<>(); - - for (AuthRecipeUserInfo user : users) { - userIds.add(user.getSupertokensUserId()); - - for (LoginMethod lm : user.loginMethods) { - userIds.add(lm.getSupertokensUserId()); - } - } - ArrayList userIdsList = new ArrayList<>(userIds); - userIdsList.addAll(userIds); - HashMap userIdMappings = getUserIdMappingForSuperTokensUserIds(appIdentifierWithStorage, - userIdsList); - - for (AuthRecipeUserInfo user : users) { - user.setExternalUserId(userIdMappings.get(user.getSupertokensUserId())); - - for (LoginMethod lm : user.loginMethods) { - lm.setExternalUserId(userIdMappings.get(lm.getSupertokensUserId())); - } - } - } - - public static void populateExternalUserIdForUsers(TenantIdentifierWithStorage tenantIdentifierWithStorage, AuthRecipeUserInfo[] users) + public static void populateExternalUserIdForUsers(Storage storage, AuthRecipeUserInfo[] users) throws StorageQueryException { Set userIds = new HashSet<>(); @@ -451,8 +435,7 @@ public static void populateExternalUserIdForUsers(TenantIdentifierWithStorage te } ArrayList userIdsList = new ArrayList<>(userIds); userIdsList.addAll(userIds); - HashMap userIdMappings = getUserIdMappingForSuperTokensUserIds(tenantIdentifierWithStorage, - userIdsList); + HashMap userIdMappings = getUserIdMappingForSuperTokensUserIds(storage, userIdsList); for (AuthRecipeUserInfo user : users) { user.setExternalUserId(userIdMappings.get(user.getSupertokensUserId())); diff --git a/src/main/java/io/supertokens/usermetadata/UserMetadata.java b/src/main/java/io/supertokens/usermetadata/UserMetadata.java index e0f0d37fd..938f6f749 100644 --- a/src/main/java/io/supertokens/usermetadata/UserMetadata.java +++ b/src/main/java/io/supertokens/usermetadata/UserMetadata.java @@ -19,11 +19,10 @@ import com.google.gson.JsonObject; import io.supertokens.Main; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.usermetadata.sqlStorage.UserMetadataSQLStorage; import io.supertokens.storageLayer.StorageLayer; @@ -41,28 +40,28 @@ public static JsonObject updateUserMetadata(Main main, Storage storage = StorageLayer.getStorage(main); try { return updateUserMetadata( - new AppIdentifierWithStorage(null, null, storage), + new AppIdentifier(null, null), storage, userId, metadataUpdate); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } - public static JsonObject updateUserMetadata(AppIdentifierWithStorage appIdentifierWithStorage, + public static JsonObject updateUserMetadata(AppIdentifier appIdentifier, Storage storage, @Nonnull String userId, @Nonnull JsonObject metadataUpdate) throws StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException { - UserMetadataSQLStorage storage = appIdentifierWithStorage.getUserMetadataStorage(); + UserMetadataSQLStorage umdStorage = StorageUtils.getUserMetadataStorage(storage); try { - return storage.startTransaction((con) -> { - JsonObject originalMetadata = storage.getUserMetadata_Transaction(appIdentifierWithStorage, con, + return umdStorage.startTransaction((con) -> { + JsonObject originalMetadata = umdStorage.getUserMetadata_Transaction(appIdentifier, con, userId); JsonObject updatedMetadata = originalMetadata == null ? new JsonObject() : originalMetadata; MetadataUtils.shallowMergeMetadataUpdate(updatedMetadata, metadataUpdate); try { - storage.setUserMetadata_Transaction(appIdentifierWithStorage, con, userId, updatedMetadata); + umdStorage.setUserMetadata_Transaction(appIdentifier, con, userId, updatedMetadata); } catch (TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(e); } @@ -80,15 +79,15 @@ public static JsonObject updateUserMetadata(AppIdentifierWithStorage appIdentifi @TestOnly public static JsonObject getUserMetadata(Main main, @Nonnull String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return getUserMetadata(new AppIdentifierWithStorage(null, null, storage), userId); + return getUserMetadata(new AppIdentifier(null, null), storage, userId); } - public static JsonObject getUserMetadata(AppIdentifierWithStorage appIdentifierWithStorage, + public static JsonObject getUserMetadata(AppIdentifier appIdentifier, Storage storage, @Nonnull String userId) throws StorageQueryException { - UserMetadataSQLStorage storage = appIdentifierWithStorage.getUserMetadataStorage(); + UserMetadataSQLStorage umdStorage = StorageUtils.getUserMetadataStorage(storage); - JsonObject metadata = storage.getUserMetadata(appIdentifierWithStorage, userId); + JsonObject metadata = umdStorage.getUserMetadata(appIdentifier, userId); if (metadata == null) { return new JsonObject(); @@ -100,11 +99,11 @@ public static JsonObject getUserMetadata(AppIdentifierWithStorage appIdentifierW @TestOnly public static void deleteUserMetadata(Main main, @Nonnull String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - deleteUserMetadata(new AppIdentifierWithStorage(null, null, storage), userId); + deleteUserMetadata(new AppIdentifier(null, null), storage, userId); } - public static void deleteUserMetadata(AppIdentifierWithStorage appIdentifierWithStorage, + public static void deleteUserMetadata(AppIdentifier appIdentifier, Storage storage, @Nonnull String userId) throws StorageQueryException { - appIdentifierWithStorage.getUserMetadataStorage().deleteUserMetadata(appIdentifierWithStorage, userId); + StorageUtils.getUserMetadataStorage(storage).deleteUserMetadata(appIdentifier, userId); } } diff --git a/src/main/java/io/supertokens/userroles/UserRoles.java b/src/main/java/io/supertokens/userroles/UserRoles.java index 6ff0b88e4..5b1f85553 100644 --- a/src/main/java/io/supertokens/userroles/UserRoles.java +++ b/src/main/java/io/supertokens/userroles/UserRoles.java @@ -18,10 +18,11 @@ import io.supertokens.Main; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.userroles.exception.DuplicateUserRoleMappingException; import io.supertokens.pluginInterface.userroles.exception.UnknownRoleException; @@ -30,15 +31,25 @@ import org.jetbrains.annotations.TestOnly; import javax.annotation.Nullable; +import java.util.Arrays; public class UserRoles { // add a role to a user and return true, if the role is already mapped to the user return false, but if // the role does not exist, throw an UNKNOWN_ROLE_EXCEPTION error - public static boolean addRoleToUser(TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId, + public static boolean addRoleToUser(Main main, TenantIdentifier tenantIdentifier, Storage storage, String userId, String role) throws StorageQueryException, UnknownRoleException, TenantOrAppNotFoundException { + + // Roles are stored in public tenant storage and role to user mapping is stored in the tenant's storage + // We do this because it's not straight forward to replicate roles to all storages of an app + Storage appStorage = StorageLayer.getStorage( + tenantIdentifier.toAppIdentifier().getAsPublicTenantIdentifier(), main); + if (!doesRoleExist(tenantIdentifier.toAppIdentifier(), appStorage, role)) { + throw new UnknownRoleException(); + } + try { - tenantIdentifierWithStorage.getUserRolesStorage().addRoleToUser(tenantIdentifierWithStorage, userId, role); + StorageUtils.getUserRolesStorage(storage).addRoleToUser(tenantIdentifier, userId, role); return true; } catch (DuplicateUserRoleMappingException e) { // user already has role @@ -52,8 +63,8 @@ public static boolean addRoleToUser(Main main, String userId, String role) Storage storage = StorageLayer.getStorage(main); try { return addRoleToUser( - new TenantIdentifierWithStorage(null, null, null, storage), - userId, role); + main, new TenantIdentifier(null, null, null), + storage, userId, role); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } @@ -62,17 +73,17 @@ public static boolean addRoleToUser(Main main, String userId, String role) // create a new role if it doesn't exist and add permissions to the role. This will create the role // in the user pool associated with the tenant used to query this API, so that this role can then // be shared across any tenant in that same user pool. - public static boolean createNewRoleOrModifyItsPermissions(AppIdentifierWithStorage appIdentifierWithStorage, + public static boolean createNewRoleOrModifyItsPermissions(AppIdentifier appIdentifier, Storage storage, String role, String[] permissions) throws StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException { - UserRolesSQLStorage storage = appIdentifierWithStorage.getUserRolesStorage(); + UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(storage); try { - return storage.startTransaction(con -> { + return userRolesStorage.startTransaction(con -> { boolean wasANewRoleCreated = false; try { - wasANewRoleCreated = storage.createNewRoleOrDoNothingIfExists_Transaction( - appIdentifierWithStorage, con, role); + wasANewRoleCreated = userRolesStorage.createNewRoleOrDoNothingIfExists_Transaction( + appIdentifier, con, role); } catch (TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(e); } @@ -80,14 +91,14 @@ public static boolean createNewRoleOrModifyItsPermissions(AppIdentifierWithStora if (permissions != null) { for (int i = 0; i < permissions.length; i++) { try { - storage.addPermissionToRoleOrDoNothingIfExists_Transaction(appIdentifierWithStorage, + userRolesStorage.addPermissionToRoleOrDoNothingIfExists_Transaction(appIdentifier, con, role, permissions[i]); } catch (UnknownRoleException e) { // ignore exception, should not come here since role should always exist in this transaction } } } - storage.commitTransaction(con); + userRolesStorage.commitTransaction(con); return wasANewRoleCreated; }); } catch (StorageTransactionLogicException e) { @@ -104,38 +115,38 @@ public static boolean createNewRoleOrModifyItsPermissions(Main main, throws StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException { Storage storage = StorageLayer.getStorage(main); return createNewRoleOrModifyItsPermissions( - new AppIdentifierWithStorage(null, null, storage), role, + new AppIdentifier(null, null), storage, role, permissions); } - public static boolean doesRoleExist(AppIdentifierWithStorage appIdentifierWithStorage, String role) + public static boolean doesRoleExist(AppIdentifier appIdentifier, Storage storage, String role) throws StorageQueryException { - UserRolesSQLStorage storage = appIdentifierWithStorage.getUserRolesStorage(); - return storage.doesRoleExist(appIdentifierWithStorage, role); + UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(storage); + return userRolesStorage.doesRoleExist(appIdentifier, role); } @TestOnly public static boolean doesRoleExist(Main main, String role) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return doesRoleExist(new AppIdentifierWithStorage(null, null, storage), role); + return doesRoleExist(new AppIdentifier(null, null), storage, role); } // remove a role mapped to a user, if the role doesn't exist throw a UNKNOWN_ROLE_EXCEPTION error - public static boolean removeUserRole(TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId, + public static boolean removeUserRole(TenantIdentifier tenantIdentifier, Storage storage, String userId, String role) throws StorageQueryException, StorageTransactionLogicException, UnknownRoleException { - UserRolesSQLStorage storage = tenantIdentifierWithStorage.getUserRolesStorage(); + UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(storage); try { - return storage.startTransaction(con -> { + return userRolesStorage.startTransaction(con -> { - boolean doesRoleExist = storage.doesRoleExist_Transaction( - tenantIdentifierWithStorage.toAppIdentifier(), con, role); + boolean doesRoleExist = userRolesStorage.doesRoleExist_Transaction( + tenantIdentifier.toAppIdentifier(), con, role); if (doesRoleExist) { - return storage.deleteRoleForUser_Transaction(tenantIdentifierWithStorage, con, userId, role); + return userRolesStorage.deleteRoleForUser_Transaction(tenantIdentifier, con, userId, role); } else { throw new StorageTransactionLogicException(new UnknownRoleException()); } @@ -153,14 +164,14 @@ public static boolean removeUserRole(Main main, String userId, String role) throws StorageQueryException, StorageTransactionLogicException, UnknownRoleException { Storage storage = StorageLayer.getStorage(main); return removeUserRole( - new TenantIdentifierWithStorage(null, null, null, storage), + new TenantIdentifier(null, null, null), storage, userId, role); } // retrieve all roles associated with the user - public static String[] getRolesForUser(TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId) + public static String[] getRolesForUser(TenantIdentifier tenantIdentifier, Storage storage, String userId) throws StorageQueryException { - return tenantIdentifierWithStorage.getUserRolesStorage().getRolesForUser(tenantIdentifierWithStorage, userId); + return StorageUtils.getUserRolesStorage(storage).getRolesForUser(tenantIdentifier, userId); } @TestOnly @@ -168,18 +179,18 @@ public static String[] getRolesForUser(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getRolesForUser( - new TenantIdentifierWithStorage(null, null, null, storage), userId); + new TenantIdentifier(null, null, null), storage, userId); } // retrieve all users who have the input role, if role does not exist then throw UNKNOWN_ROLE_EXCEPTION - public static String[] getUsersForRole(TenantIdentifierWithStorage tenantIdentifierWithStorage, String role) + public static String[] getUsersForRole(TenantIdentifier tenantIdentifier, Storage storage, String role) throws StorageQueryException, UnknownRoleException { // Since getUsersForRole does not change any data we do not use a transaction since it would not solve any // problem - UserRolesSQLStorage storage = tenantIdentifierWithStorage.getUserRolesStorage(); - boolean doesRoleExist = storage.doesRoleExist(tenantIdentifierWithStorage.toAppIdentifier(), role); + UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(storage); + boolean doesRoleExist = userRolesStorage.doesRoleExist(tenantIdentifier.toAppIdentifier(), role); if (doesRoleExist) { - return storage.getUsersForRole(tenantIdentifierWithStorage, role); + return userRolesStorage.getUsersForRole(tenantIdentifier, role); } else { throw new UnknownRoleException(); } @@ -190,20 +201,20 @@ public static String[] getUsersForRole(Main main, String role) throws StorageQueryException, UnknownRoleException { Storage storage = StorageLayer.getStorage(main); return getUsersForRole( - new TenantIdentifierWithStorage(null, null, null, storage), role); + new TenantIdentifier(null, null, null), storage, role); } // retrieve all permissions associated with the role - public static String[] getPermissionsForRole(AppIdentifierWithStorage appIdentifierWithStorage, String role) + public static String[] getPermissionsForRole(AppIdentifier appIdentifier, Storage storage, String role) throws StorageQueryException, UnknownRoleException { // Since getPermissionsForRole does not change any data we do not use a transaction since it would not solve any // problem - UserRolesSQLStorage storage = appIdentifierWithStorage.getUserRolesStorage(); - boolean doesRoleExist = storage.doesRoleExist(appIdentifierWithStorage, role); + UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(storage); + boolean doesRoleExist = userRolesStorage.doesRoleExist(appIdentifier, role); if (doesRoleExist) { - return appIdentifierWithStorage.getUserRolesStorage() - .getPermissionsForRole(appIdentifierWithStorage, role); + return StorageUtils.getUserRolesStorage(storage) + .getPermissionsForRole(appIdentifier, role); } else { throw new UnknownRoleException(); } @@ -214,30 +225,30 @@ public static String[] getPermissionsForRole(Main main, String role) throws StorageQueryException, UnknownRoleException { Storage storage = StorageLayer.getStorage(main); return getPermissionsForRole( - new AppIdentifierWithStorage(null, null, storage), role); + new AppIdentifier(null, null), storage, role); } // delete permissions from a role, if the role doesn't exist throw an UNKNOWN_ROLE_EXCEPTION - public static void deletePermissionsFromRole(AppIdentifierWithStorage appIdentifierWithStorage, String role, + public static void deletePermissionsFromRole(AppIdentifier appIdentifier, Storage storage, String role, @Nullable String[] permissions) throws StorageQueryException, StorageTransactionLogicException, UnknownRoleException { - UserRolesSQLStorage storage = appIdentifierWithStorage.getUserRolesStorage(); + UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(storage); try { - storage.startTransaction(con -> { - boolean doesRoleExist = storage.doesRoleExist_Transaction(appIdentifierWithStorage, con, role); + userRolesStorage.startTransaction(con -> { + boolean doesRoleExist = userRolesStorage.doesRoleExist_Transaction(appIdentifier, con, role); if (doesRoleExist) { if (permissions == null) { - storage.deleteAllPermissionsForRole_Transaction(appIdentifierWithStorage, con, role); + userRolesStorage.deleteAllPermissionsForRole_Transaction(appIdentifier, con, role); } else { for (int i = 0; i < permissions.length; i++) { - storage.deletePermissionForRole_Transaction(appIdentifierWithStorage, con, role, + userRolesStorage.deletePermissionForRole_Transaction(appIdentifier, con, role, permissions[i]); } } } else { throw new StorageTransactionLogicException(new UnknownRoleException()); } - storage.commitTransaction(con); + userRolesStorage.commitTransaction(con); return null; }); } catch (StorageTransactionLogicException e) { @@ -253,16 +264,16 @@ public static void deletePermissionsFromRole(Main main, String role, @Nullable String[] permissions) throws StorageQueryException, StorageTransactionLogicException, UnknownRoleException { Storage storage = StorageLayer.getStorage(main); - deletePermissionsFromRole(new AppIdentifierWithStorage(null, null, storage), + deletePermissionsFromRole(new AppIdentifier(null, null), storage, role, permissions); } // retrieve roles that have the input permission - public static String[] getRolesThatHavePermission(AppIdentifierWithStorage appIdentifierWithStorage, + public static String[] getRolesThatHavePermission(AppIdentifier appIdentifier, Storage storage, String permission) throws StorageQueryException { - return appIdentifierWithStorage.getUserRolesStorage().getRolesThatHavePermission( - appIdentifierWithStorage, permission); + return StorageUtils.getUserRolesStorage(storage).getRolesThatHavePermission( + appIdentifier, permission); } @TestOnly @@ -270,38 +281,52 @@ public static String[] getRolesThatHavePermission(Main main, String permission) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getRolesThatHavePermission( - new AppIdentifierWithStorage(null, null, storage), permission); + new AppIdentifier(null, null), storage, permission); } // delete a role - public static boolean deleteRole(AppIdentifierWithStorage appIdentifierWithStorage, String role) - throws StorageQueryException { - return appIdentifierWithStorage.getUserRolesStorage().deleteRole(appIdentifierWithStorage, role); + public static boolean deleteRole(Main main, AppIdentifier appIdentifier, String role) + throws StorageQueryException, TenantOrAppNotFoundException { + + Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier); + boolean deletedRole = false; + for (Storage storage : storages) { + UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(storage); + deletedRole = userRolesStorage.deleteAllUserRoleAssociationsForRole(appIdentifier, role) || deletedRole; + } + + // Delete the role from the public tenant storage in the end so that the user + // never sees a role for user that has been deleted while the deletion is in progress + Storage appStorage = StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main); + UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(appStorage); + deletedRole = userRolesStorage.deleteRole(appIdentifier, role) || deletedRole; + + return deletedRole; } @TestOnly - public static boolean deleteRole(Main main, String role) throws StorageQueryException { - Storage storage = StorageLayer.getStorage(main); - return deleteRole(new AppIdentifierWithStorage(null, null, storage), role); + public static boolean deleteRole(Main main, String role) throws StorageQueryException, + TenantOrAppNotFoundException { + return deleteRole(main, new AppIdentifier(null, null), role); } // retrieve all roles that have been created - public static String[] getRoles(AppIdentifierWithStorage appIdentifierWithStorage) + public static String[] getRoles(AppIdentifier appIdentifier, Storage storage) throws StorageQueryException { - return appIdentifierWithStorage.getUserRolesStorage().getRoles(appIdentifierWithStorage); + return StorageUtils.getUserRolesStorage(storage).getRoles(appIdentifier); } @TestOnly public static String[] getRoles(Main main) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return getRoles(new AppIdentifierWithStorage(null, null, storage)); + return getRoles(new AppIdentifier(null, null), storage); } // delete all roles associated with a user - public static int deleteAllRolesForUser(TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId) + public static int deleteAllRolesForUser(TenantIdentifier tenantIdentifier, Storage storage, String userId) throws StorageQueryException { - return tenantIdentifierWithStorage.getUserRolesStorage().deleteAllRolesForUser( - tenantIdentifierWithStorage, userId); + return StorageUtils.getUserRolesStorage(storage).deleteAllRolesForUser( + tenantIdentifier, userId); } @TestOnly @@ -309,7 +334,7 @@ public static int deleteAllRolesForUser(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return deleteAllRolesForUser( - new TenantIdentifierWithStorage(null, null, null, storage), userId); + new TenantIdentifier(null, null, null), storage, userId); } } diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 3a593fbb0..f6c564b9e 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -24,7 +24,6 @@ import io.supertokens.exceptions.QuitProgramException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.api.accountlinking.*; import io.supertokens.webserver.api.core.*; @@ -144,7 +143,7 @@ public void start() { tomcat.start(); } catch (LifecycleException e) { // reusing same port OR not right permissions given. - Logging.error(main, TenantIdentifierWithStorage.BASE_TENANT, null, false, e); + Logging.error(main, TenantIdentifier.BASE_TENANT, null, false, e); throw new QuitProgramException( "Error while starting webserver. Possible reasons:\n- Another instance of SuperTokens is already " + "running on the same port. If you want to run another instance, please pass a new config " diff --git a/src/main/java/io/supertokens/webserver/WebserverAPI.java b/src/main/java/io/supertokens/webserver/WebserverAPI.java index 03919bc10..c88548fce 100644 --- a/src/main/java/io/supertokens/webserver/WebserverAPI.java +++ b/src/main/java/io/supertokens/webserver/WebserverAPI.java @@ -17,9 +17,8 @@ package io.supertokens.webserver; import com.google.gson.JsonElement; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; -import io.supertokens.TenantIdentifierWithStorageAndUserIdMapping; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.config.Config; import io.supertokens.config.CoreConfig; import io.supertokens.exceptions.QuitProgramException; @@ -30,10 +29,7 @@ import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.useridmapping.UserIdType; @@ -46,7 +42,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.catalina.filters.RemoteAddrFilter; -import org.jetbrains.annotations.TestOnly; import java.io.IOException; import java.util.HashSet; @@ -89,7 +84,7 @@ public SemVer getLatestCDIVersionForRequest(HttpServletRequest req) throws ServletException, TenantOrAppNotFoundException { SemVer maxCDIVersion = getLatestCDIVersion(); String maxCDIVersionStr = Config.getConfig( - getAppIdentifierWithStorage(req).getAsPublicTenantIdentifier(), main).getMaxCDIVersion(); + getAppIdentifierWithoutVerifying(req).getAsPublicTenantIdentifier(), main).getMaxCDIVersion(); if (maxCDIVersionStr != null) { maxCDIVersion = new SemVer(maxCDIVersionStr); } @@ -304,78 +299,89 @@ private String getConnectionUriDomain(HttpServletRequest req) throws ServletExce return null; } - @TestOnly - protected TenantIdentifier getTenantIdentifierFromRequest(HttpServletRequest req) throws ServletException { + private TenantIdentifier getTenantIdentifierWithoutVerifying(HttpServletRequest req) throws ServletException { return new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req)); } - protected TenantIdentifierWithStorage getTenantIdentifierWithStorageFromRequest(HttpServletRequest req) - throws TenantOrAppNotFoundException, ServletException { - TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), - this.getTenantId(req)); - Storage storage = StorageLayer.getStorage(tenantIdentifier, main); - return tenantIdentifier.withStorage(storage); + protected TenantIdentifier getTenantIdentifier(HttpServletRequest req) + throws ServletException, TenantOrAppNotFoundException { + getTenantStorage(req); // ensure the tenant exists + return new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req)); + } + + private AppIdentifier getAppIdentifierWithoutVerifying(HttpServletRequest req) throws ServletException { + return new AppIdentifier(this.getConnectionUriDomain(req), this.getAppId(req)); } - protected AppIdentifierWithStorage getAppIdentifierWithStorage(HttpServletRequest req) + protected AppIdentifier getAppIdentifier(HttpServletRequest req) + throws ServletException, TenantOrAppNotFoundException { + AppIdentifier appIdentifier = getAppIdentifierWithoutVerifying(req); + StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main); // ensure the app exists + return appIdentifier; + } + + protected Storage getTenantStorage(HttpServletRequest req) throws TenantOrAppNotFoundException, ServletException { TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req)); + return StorageLayer.getStorage(tenantIdentifier, main); + } - Storage storage = StorageLayer.getStorage(tenantIdentifier, main); - Storage[] storages = StorageLayer.getStoragesForApp(main, tenantIdentifier.toAppIdentifier()); + protected Storage[] enforcePublicTenantAndGetAllStoragesForApp(HttpServletRequest req) + throws ServletException, BadPermissionException, TenantOrAppNotFoundException { + if (getTenantId(req) != null) { + throw new BadPermissionException("Only public tenantId can call this app specific API"); + } - return new AppIdentifierWithStorage(tenantIdentifier.getConnectionUriDomain(), tenantIdentifier.getAppId(), - storage, storages); + AppIdentifier appIdentifier = getAppIdentifierWithoutVerifying(req); + return StorageLayer.getStoragesForApp(main, appIdentifier); } - protected AppIdentifierWithStorage getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant( + protected Storage enforcePublicTenantAndGetPublicTenantStorage( HttpServletRequest req) throws TenantOrAppNotFoundException, BadPermissionException, ServletException { TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req)); - if (!tenantIdentifier.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID)) { - throw new BadPermissionException("Only public tenantId can query across tenants"); + if (getTenantId(req) != null) { + throw new BadPermissionException("Only public tenantId can call this app specific API"); } - Storage storage = StorageLayer.getStorage(tenantIdentifier, main); - Storage[] storages = StorageLayer.getStoragesForApp(main, tenantIdentifier.toAppIdentifier()); - return new AppIdentifierWithStorage(tenantIdentifier.getConnectionUriDomain(), tenantIdentifier.getAppId(), - storage, storages); - } - - protected AppIdentifierWithStorage getPublicTenantStorage(HttpServletRequest req) - throws ServletException, TenantOrAppNotFoundException { - AppIdentifier appIdentifier = new AppIdentifier(this.getConnectionUriDomain(req), this.getAppId(req)); - - Storage storage = StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main); - - return appIdentifier.withStorage(storage); + return StorageLayer.getStorage(tenantIdentifier, main); } - protected TenantIdentifierWithStorageAndUserIdMapping getTenantIdentifierWithStorageAndUserIdMappingFromRequest( + protected StorageAndUserIdMapping getStorageAndUserIdMappingForTenantSpecificApi( HttpServletRequest req, String userId, UserIdType userIdType) throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException, ServletException { TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req)); - return StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(main, tenantIdentifier, userId, + return StorageLayer.findStorageAndUserIdMappingForUser(main, tenantIdentifier, userId, userIdType); } - protected AppIdentifierWithStorageAndUserIdMapping getAppIdentifierWithStorageAndUserIdMappingFromRequest( - HttpServletRequest req, String userId, UserIdType userIdType) - throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException, ServletException { - // This function uses storage of the tenent from which the request came from as a priorityStorage + protected StorageAndUserIdMapping enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + HttpServletRequest req, String userId, UserIdType userIdType, boolean isCallFromAuthRecipeAPI) + throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException, ServletException, + BadPermissionException { + // This function uses storage of the tenant from which the request came from as a priorityStorage // while searching for the user across all storages for the app - AppIdentifierWithStorage appIdentifierWithStorage = getAppIdentifierWithStorage(req); - return StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage(main, - appIdentifierWithStorage, appIdentifierWithStorage.getStorage(), userId, userIdType); + AppIdentifier appIdentifier = getAppIdentifierWithoutVerifying(req); + Storage[] storages = enforcePublicTenantAndGetAllStoragesForApp(req); + try { + return StorageLayer.findStorageAndUserIdMappingForUser( + appIdentifier, storages, userId, userIdType); + } catch (UnknownUserIdException e) { + if (isCallFromAuthRecipeAPI) { + throw e; + } + + return new StorageAndUserIdMapping(enforcePublicTenantAndGetPublicTenantStorage(req), null); + } } protected boolean checkIPAccess(HttpServletRequest req, HttpServletResponse resp) throws TenantOrAppNotFoundException, ServletException, IOException { - CoreConfig config = Config.getConfig(getTenantIdentifierWithStorageFromRequest(req), main); + CoreConfig config = Config.getConfig(getTenantIdentifierWithoutVerifying(req), main); String allow = config.getIpAllowRegex(); String deny = config.getIpDenyRegex(); if (allow == null && deny == null) { @@ -417,16 +423,7 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws TenantIdentifier tenantIdentifier = null; try { - try { - tenantIdentifier = getTenantIdentifierWithStorageFromRequest(req); - } catch (TenantOrAppNotFoundException e) { - // we do this so that the logs that are printed out have the right "Tenant(.." info in them, - // otherwise it will assume the base tenant (with "" CUD), which may not be the one querying - // this API right now. - tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), - this.getTenantId(req)); - throw e; - } + tenantIdentifier = getTenantIdentifierWithoutVerifying(req); if (!this.checkIPAccess(req, resp)) { // IP access denied and the filter has already sent the response diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java index 9457ddd0d..a72f0d6d3 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java @@ -17,15 +17,17 @@ package io.supertokens.webserver.api.accountlinking; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -53,24 +55,31 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // API is app specific String inputRecipeUserId = InputParser.getQueryParamOrThrowError(req, "recipeUserId", false); - AppIdentifierWithStorage appIdentifierWithStorage = null; + AppIdentifier appIdentifier = null; + try { + appIdentifier = this.getAppIdentifier(req); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + Storage storage = null; + try { String userId = inputRecipeUserId; - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, inputRecipeUserId, UserIdType.ANY); - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; + StorageAndUserIdMapping storageAndMapping = + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, inputRecipeUserId, UserIdType.ANY, true); + storage = storageAndMapping.storage; + if (storageAndMapping.userIdMapping != null) { + userId = storageAndMapping.userIdMapping.superTokensUserId; } - appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.canCreatePrimaryUser(appIdentifierWithStorage, + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.canCreatePrimaryUser(appIdentifier, storage, userId); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("wasAlreadyAPrimaryUser", result.wasAlreadyAPrimaryUser); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { throw new ServletException(new BadRequestException("Unknown user ID provided")); @@ -79,7 +88,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject response = new JsonObject(); response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( - appIdentifierWithStorage, e.primaryUserId, + appIdentifier, storage, e.primaryUserId, UserIdType.SUPERTOKENS); if (result != null) { response.addProperty("primaryUserId", result.externalUserId); @@ -96,7 +105,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject response = new JsonObject(); response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"); io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( - appIdentifierWithStorage, e.primaryUserId, + appIdentifier, storage, e.primaryUserId, UserIdType.SUPERTOKENS); if (result != null) { response.addProperty("primaryUserId", result.externalUserId); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java index 97c10f2c6..75e416eb9 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java @@ -17,17 +17,19 @@ package io.supertokens.webserver.api.accountlinking; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -56,48 +58,54 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO String inputRecipeUserId = InputParser.getQueryParamOrThrowError(req, "recipeUserId", false); String inputPrimaryUserId = InputParser.getQueryParamOrThrowError(req, "primaryUserId", false); - AppIdentifierWithStorage primaryUserIdAppIdentifierWithStorage = null; - AppIdentifierWithStorage recipeUserIdAppIdentifierWithStorage = null; + AppIdentifier appIdentifier = null; + try { + appIdentifier = this.getAppIdentifier(req); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + Storage primaryUserIdStorage = null; + Storage recipeUserIdStorage = null; try { String recipeUserId = inputRecipeUserId; { - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, inputRecipeUserId, UserIdType.ANY); + StorageAndUserIdMapping mappingAndStorage = + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, inputRecipeUserId, UserIdType.ANY, true); if (mappingAndStorage.userIdMapping != null) { recipeUserId = mappingAndStorage.userIdMapping.superTokensUserId; } - recipeUserIdAppIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + recipeUserIdStorage = mappingAndStorage.storage; } String primaryUserId = inputPrimaryUserId; { - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, inputPrimaryUserId, UserIdType.ANY); + StorageAndUserIdMapping mappingAndStorage = + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, inputPrimaryUserId, UserIdType.ANY, true); if (mappingAndStorage.userIdMapping != null) { primaryUserId = mappingAndStorage.userIdMapping.superTokensUserId; } - primaryUserIdAppIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + primaryUserIdStorage = mappingAndStorage.storage; } // we do a check based on user pool ID and not instance reference checks cause the user // could be in the same db, but their storage layers may just have different - if (!primaryUserIdAppIdentifierWithStorage.getStorage().getUserPoolId().equals( - recipeUserIdAppIdentifierWithStorage.getStorage().getUserPoolId())) { + if (!primaryUserIdStorage.getUserPoolId().equals( + recipeUserIdStorage.getUserPoolId())) { throw new ServletException( new BadRequestException( "Cannot link users that are parts of different databases. Different pool IDs: " + - primaryUserIdAppIdentifierWithStorage.getStorage().getUserPoolId() + " AND " + - recipeUserIdAppIdentifierWithStorage.getStorage().getUserPoolId())); + primaryUserIdStorage.getUserPoolId() + " AND " + + recipeUserIdStorage.getUserPoolId())); } - AuthRecipe.CanLinkAccountsResult result = AuthRecipe.canLinkAccounts(primaryUserIdAppIdentifierWithStorage, + AuthRecipe.CanLinkAccountsResult result = AuthRecipe.canLinkAccounts(appIdentifier, primaryUserIdStorage, recipeUserId, primaryUserId); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("accountsAlreadyLinked", result.alreadyLinked); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { throw new ServletException(new BadRequestException("Unknown user ID provided")); @@ -106,7 +114,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject response = new JsonObject(); response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( - primaryUserIdAppIdentifierWithStorage, e.primaryUserId, + appIdentifier, primaryUserIdStorage, e.primaryUserId, UserIdType.SUPERTOKENS); if (result != null) { response.addProperty("primaryUserId", result.externalUserId); @@ -122,7 +130,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO try { JsonObject response = new JsonObject(); response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); - UserIdMapping.populateExternalUserIdForUsers(recipeUserIdAppIdentifierWithStorage, new AuthRecipeUserInfo[]{e.recipeUser}); + UserIdMapping.populateExternalUserIdForUsers(recipeUserIdStorage, + new AuthRecipeUserInfo[]{e.recipeUser}); response.addProperty("primaryUserId", e.recipeUser.getSupertokensOrExternalUserId()); response.addProperty("description", e.getMessage()); super.sendJsonResponse(200, response, resp); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java index 8573650b7..d275b2aa1 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java @@ -17,16 +17,18 @@ package io.supertokens.webserver.api.accountlinking; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -55,18 +57,25 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String inputRecipeUserId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); - AppIdentifierWithStorage appIdentifierWithStorage = null; + AppIdentifier appIdentifier = null; + try { + appIdentifier = this.getAppIdentifier(req); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + Storage storage = null; + try { String userId = inputRecipeUserId; - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, inputRecipeUserId, UserIdType.ANY); + StorageAndUserIdMapping mappingAndStorage = + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, inputRecipeUserId, UserIdType.ANY, true); + storage = mappingAndStorage.storage; if (mappingAndStorage.userIdMapping != null) { userId = mappingAndStorage.userIdMapping.superTokensUserId; } - appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(main, appIdentifierWithStorage, + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(main, appIdentifier, storage, userId); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); @@ -78,7 +87,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } response.add("user", result.user.toJson()); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException | + BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { throw new ServletException(new BadRequestException("Unknown user ID provided")); @@ -87,7 +97,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject response = new JsonObject(); response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( - appIdentifierWithStorage, e.primaryUserId, + appIdentifier, storage, e.primaryUserId, UserIdType.SUPERTOKENS); if (result != null) { response.addProperty("primaryUserId", result.externalUserId); @@ -104,7 +114,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject response = new JsonObject(); response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"); io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( - appIdentifierWithStorage, e.primaryUserId, + appIdentifier, storage, e.primaryUserId, UserIdType.SUPERTOKENS); if (result != null) { response.addProperty("primaryUserId", result.externalUserId); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java index 76a9d4dfe..0fa6fbb7f 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java @@ -17,20 +17,20 @@ package io.supertokens.webserver.api.accountlinking; import com.google.gson.JsonObject; -import io.supertokens.ActiveUsers; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -41,8 +41,6 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; public class LinkAccountsAPI extends WebserverAPI { @@ -62,52 +60,59 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I String inputRecipeUserId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); String inputPrimaryUserId = InputParser.parseStringOrThrowError(input, "primaryUserId", false); - AppIdentifierWithStorage primaryUserIdAppIdentifierWithStorage = null; - AppIdentifierWithStorage recipeUserIdAppIdentifierWithStorage = null; + AppIdentifier appIdentifier = null; + try { + appIdentifier = getAppIdentifier(req); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + Storage primaryUserIdStorage = null; + Storage recipeUserIdStorage = null; try { String recipeUserId = inputRecipeUserId; { - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, inputRecipeUserId, UserIdType.ANY); + StorageAndUserIdMapping mappingAndStorage = + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, inputRecipeUserId, UserIdType.ANY, true); if (mappingAndStorage.userIdMapping != null) { recipeUserId = mappingAndStorage.userIdMapping.superTokensUserId; } - recipeUserIdAppIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + recipeUserIdStorage = mappingAndStorage.storage; } String primaryUserId = inputPrimaryUserId; { - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, inputPrimaryUserId, UserIdType.ANY); + StorageAndUserIdMapping mappingAndStorage = + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, inputPrimaryUserId, UserIdType.ANY, true); if (mappingAndStorage.userIdMapping != null) { primaryUserId = mappingAndStorage.userIdMapping.superTokensUserId; } - primaryUserIdAppIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + primaryUserIdStorage = mappingAndStorage.storage; } // we do a check based on user pool ID and not instance reference checks cause the user // could be in the same db, but their storage layers may just have different - if (!primaryUserIdAppIdentifierWithStorage.getStorage().getUserPoolId().equals( - recipeUserIdAppIdentifierWithStorage.getStorage().getUserPoolId())) { + if (!primaryUserIdStorage.getUserPoolId().equals( + recipeUserIdStorage.getUserPoolId())) { throw new ServletException( new BadRequestException( "Cannot link users that are parts of different databases. Different pool IDs: " + - primaryUserIdAppIdentifierWithStorage.getStorage().getUserPoolId() + " AND " + - recipeUserIdAppIdentifierWithStorage.getStorage().getUserPoolId())); + primaryUserIdStorage.getUserPoolId() + " AND " + + recipeUserIdStorage.getUserPoolId())); } AuthRecipe.LinkAccountsResult linkAccountsResult = AuthRecipe.linkAccounts(main, - primaryUserIdAppIdentifierWithStorage, + appIdentifier, primaryUserIdStorage, recipeUserId, primaryUserId); - UserIdMapping.populateExternalUserIdForUsers(primaryUserIdAppIdentifierWithStorage, new AuthRecipeUserInfo[]{linkAccountsResult.user}); + UserIdMapping.populateExternalUserIdForUsers(primaryUserIdStorage, new AuthRecipeUserInfo[]{linkAccountsResult.user}); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("accountsAlreadyLinked", linkAccountsResult.wasAlreadyLinked); response.add("user", linkAccountsResult.user.toJson()); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException | + BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { throw new ServletException(new BadRequestException("Unknown user ID provided")); @@ -116,7 +121,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject response = new JsonObject(); response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( - primaryUserIdAppIdentifierWithStorage, e.primaryUserId, + appIdentifier, primaryUserIdStorage, e.primaryUserId, UserIdType.SUPERTOKENS); if (result != null) { response.addProperty("primaryUserId", result.externalUserId); @@ -132,7 +137,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { JsonObject response = new JsonObject(); response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); - UserIdMapping.populateExternalUserIdForUsers(recipeUserIdAppIdentifierWithStorage, new AuthRecipeUserInfo[]{e.recipeUser}); + UserIdMapping.populateExternalUserIdForUsers(recipeUserIdStorage, new AuthRecipeUserInfo[]{e.recipeUser}); response.addProperty("primaryUserId", e.recipeUser.getSupertokensOrExternalUserId()); response.addProperty("description", e.getMessage()); response.add("user", e.recipeUser.toJson()); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java index 3abed0328..67d214646 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java @@ -17,14 +17,16 @@ package io.supertokens.webserver.api.accountlinking; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdType; import io.supertokens.webserver.InputParser; @@ -52,25 +54,30 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String inputRecipeUserId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); - AppIdentifierWithStorage appIdentifierWithStorage = null; + AppIdentifier appIdentifier = null; + try { + appIdentifier = getAppIdentifier(req); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + Storage storage = null; try { String userId = inputRecipeUserId; - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, inputRecipeUserId, UserIdType.ANY); + StorageAndUserIdMapping mappingAndStorage = + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, inputRecipeUserId, UserIdType.ANY, true); if (mappingAndStorage.userIdMapping != null) { userId = mappingAndStorage.userIdMapping.superTokensUserId; } - appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + storage = mappingAndStorage.storage; - boolean wasDeleted = AuthRecipe.unlinkAccounts(main, appIdentifierWithStorage, - userId); + boolean wasDeleted = AuthRecipe.unlinkAccounts(main, appIdentifier, storage, userId); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("wasRecipeUserDeleted", wasDeleted); response.addProperty("wasLinked", true); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { throw new ServletException(new BadRequestException("Unknown user ID provided")); diff --git a/src/main/java/io/supertokens/webserver/api/core/ActiveUsersCountAPI.java b/src/main/java/io/supertokens/webserver/api/core/ActiveUsersCountAPI.java index d69a34015..927e9c68c 100644 --- a/src/main/java/io/supertokens/webserver/api/core/ActiveUsersCountAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/ActiveUsersCountAPI.java @@ -52,8 +52,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - int count = ActiveUsers.countUsersActiveSince( - this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req), main, sinceTimestamp); + enforcePublicTenantAndGetPublicTenantStorage(req); // to enforce this API is called from public tenant + int count = ActiveUsers.countUsersActiveSince(main, this.getAppIdentifier(req), sinceTimestamp); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); result.addProperty("count", count); diff --git a/src/main/java/io/supertokens/webserver/api/core/ConfigAPI.java b/src/main/java/io/supertokens/webserver/api/core/ConfigAPI.java index 8b997fc56..6c9abfe14 100644 --- a/src/main/java/io/supertokens/webserver/api/core/ConfigAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/ConfigAPI.java @@ -52,14 +52,15 @@ protected boolean checkAPIKey(HttpServletRequest req) { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { String pid = InputParser.getQueryParamOrThrowError(req, "pid", false); + TenantIdentifier tenantIdentifier = null; try { - TenantIdentifier tenantIdentifier = getTenantIdentifierWithStorageFromRequest(req); - if (!tenantIdentifier.equals(new TenantIdentifier(null, null, null))) { - throw new ServletException(new BadPermissionException("you can call this only from the base connection uri domain, public app and tenant")); - } + tenantIdentifier = getTenantIdentifier(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } + if (!tenantIdentifier.equals(new TenantIdentifier(null, null, null))) { + throw new ServletException(new BadPermissionException("you can call this only from the base connection uri domain, public app and tenant")); + } if ((ProcessHandle.current().pid() + "").equals(pid)) { String path = CLIOptions.get(main).getConfigFilePath() == null diff --git a/src/main/java/io/supertokens/webserver/api/core/DeleteUserAPI.java b/src/main/java/io/supertokens/webserver/api/core/DeleteUserAPI.java index ce51410f3..5d7323bbe 100644 --- a/src/main/java/io/supertokens/webserver/api/core/DeleteUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/DeleteUserAPI.java @@ -17,9 +17,10 @@ package io.supertokens.webserver.api.core; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; @@ -58,13 +59,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, true); - AuthRecipe.deleteUser(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId, + AuthRecipe.deleteUser(getAppIdentifier(req), storageAndUserIdMapping.storage, userId, removeAllLinkedAccounts, - appIdentifierWithStorageAndUserIdMapping.userIdMapping); - } catch (StorageQueryException | TenantOrAppNotFoundException | StorageTransactionLogicException e) { + storageAndUserIdMapping.userIdMapping); + } catch (StorageQueryException | TenantOrAppNotFoundException | StorageTransactionLogicException | + BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { // Do nothing diff --git a/src/main/java/io/supertokens/webserver/api/core/EEFeatureFlagAPI.java b/src/main/java/io/supertokens/webserver/api/core/EEFeatureFlagAPI.java index 1094c372b..d3e250365 100644 --- a/src/main/java/io/supertokens/webserver/api/core/EEFeatureFlagAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/EEFeatureFlagAPI.java @@ -24,7 +24,7 @@ import io.supertokens.featureflag.FeatureFlag; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -49,9 +49,11 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific and can be queried only from public tenant try { - EE_FEATURES[] features = FeatureFlag.getInstance(main, this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req)) + AppIdentifier appIdentifier = this.getAppIdentifier(req); + this.enforcePublicTenantAndGetPublicTenantStorage(req); // enforce public tenant + EE_FEATURES[] features = FeatureFlag.getInstance(main, appIdentifier) .getEnabledFeatures(); - JsonObject stats = FeatureFlag.getInstance(main, this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req)) + JsonObject stats = FeatureFlag.getInstance(main, appIdentifier) .getPaidFeatureStats(); JsonObject result = new JsonObject(); JsonArray featuresJson = new JsonArray(); diff --git a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java index e2562c8fa..a09155701 100644 --- a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java @@ -17,12 +17,14 @@ package io.supertokens.webserver.api.core; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -54,19 +56,22 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO AuthRecipeUserInfo user = null; try { - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); + AppIdentifier appIdentifier = this.getAppIdentifier(req); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(req, userId, + UserIdType.ANY, true); // if a userIdMapping exists, pass the superTokensUserId to the getUserUsingId function - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - userId = appIdentifierWithStorageAndUserIdMapping.userIdMapping.superTokensUserId; + if (storageAndUserIdMapping.userIdMapping != null) { + userId = storageAndUserIdMapping.userIdMapping.superTokensUserId; } - user = AuthRecipe.getUserById(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, + user = AuthRecipe.getUserById(appIdentifier, storageAndUserIdMapping.storage, userId); // if a userIdMapping exists, set the userId in the response to the externalUserId if (user != null) { - UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); + UserIdMapping.populateExternalUserIdForUsers( + storageAndUserIdMapping.storage, new AuthRecipeUserInfo[]{user}); } } catch (UnknownUserIdException e) { @@ -87,7 +92,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO super.sendJsonResponse(200, result, resp); } - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } diff --git a/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java b/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java index 43887590b..a4c219bce 100644 --- a/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/HelloAPI.java @@ -19,8 +19,9 @@ import io.supertokens.Main; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.RateLimiter; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -78,7 +79,10 @@ private void handleRequest(HttpServletRequest req, HttpServletResponse resp) thr // API is app specific try { - RateLimiter rateLimiter = RateLimiter.getInstance(getAppIdentifierWithStorage(req), super.main, 200); + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier); // throws tenantOrAppNotFoundException + + RateLimiter rateLimiter = RateLimiter.getInstance(appIdentifier, super.main, 200); if (!rateLimiter.checkRequest()) { if (Main.isTesting) { super.sendTextResponse(200, "RateLimitedHello", resp); @@ -88,12 +92,10 @@ private void handleRequest(HttpServletRequest req, HttpServletResponse resp) thr return; } - AppIdentifierWithStorage appIdentifierWithStorage = getAppIdentifierWithStorage(req); - - for (Storage storage : appIdentifierWithStorage.getStorages()) { + for (Storage storage : storages) { // even if the public tenant does not exist, the following function will return a null // idea here is to test that the storage is working - storage.getKeyValue(appIdentifierWithStorage.getAsPublicTenantIdentifier(), "Test"); + storage.getKeyValue(appIdentifier.getAsPublicTenantIdentifier(), "Test"); } super.sendTextResponse(200, "Hello", resp); } catch (StorageQueryException | TenantOrAppNotFoundException e) { diff --git a/src/main/java/io/supertokens/webserver/api/core/JWKSPublicAPI.java b/src/main/java/io/supertokens/webserver/api/core/JWKSPublicAPI.java index 8d1d4ee3c..285f75c69 100644 --- a/src/main/java/io/supertokens/webserver/api/core/JWKSPublicAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/JWKSPublicAPI.java @@ -61,7 +61,7 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { try { - List jwks = SigningKeys.getInstance(this.getAppIdentifierWithStorage(req), main).getJWKS(); + List jwks = SigningKeys.getInstance(this.getAppIdentifier(req), main).getJWKS(); JsonObject reply = new JsonObject(); JsonArray jwksJsonArray = new JsonParser().parse(new Gson().toJson(jwks)).getAsJsonArray(); reply.add("keys", jwksJsonArray); diff --git a/src/main/java/io/supertokens/webserver/api/core/LicenseKeyAPI.java b/src/main/java/io/supertokens/webserver/api/core/LicenseKeyAPI.java index b9960801f..1d11d07f5 100644 --- a/src/main/java/io/supertokens/webserver/api/core/LicenseKeyAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/LicenseKeyAPI.java @@ -24,7 +24,7 @@ import io.supertokens.httpRequest.HttpResponseException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -51,12 +51,14 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String licenseKey = InputParser.parseStringOrThrowError(input, "licenseKey", true); try { + AppIdentifier appIdentifier = this.getAppIdentifier(req); + this.enforcePublicTenantAndGetPublicTenantStorage(req); // enforce public tenant boolean success = false; if (licenseKey != null) { - success = FeatureFlag.getInstance(main, this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req)) + success = FeatureFlag.getInstance(main, appIdentifier) .setLicenseKeyAndSyncFeatures(licenseKey); } else { - success = FeatureFlag.getInstance(main, this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req)) + success = FeatureFlag.getInstance(main, appIdentifier) .syncFeatureFlagWithLicenseKey(); } JsonObject result = new JsonObject(); @@ -75,7 +77,8 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific and can be queried only from public tenant try { - FeatureFlag.getInstance(main, this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req)) + this.enforcePublicTenantAndGetPublicTenantStorage(req); // enforce public tenant + FeatureFlag.getInstance(main, getAppIdentifier(req)) .removeLicenseKeyAndSyncFeatures(); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -89,7 +92,8 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific and can be queried only from public tenant try { - String licenseKey = FeatureFlag.getInstance(main, this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req)) + this.enforcePublicTenantAndGetPublicTenantStorage(req); // enforce public tenant + String licenseKey = FeatureFlag.getInstance(main, getAppIdentifier(req)) .getLicenseKey(); JsonObject result = new JsonObject(); result.addProperty("licenseKey", licenseKey); diff --git a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java index 9cb27d0a8..4c002bed2 100644 --- a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java @@ -20,12 +20,12 @@ import com.google.gson.JsonObject; import io.supertokens.Main; import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; -import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -73,11 +73,12 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - AppIdentifierWithStorage appIdentifierWithStorage = this.getAppIdentifierWithStorage(req); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = this.getTenantStorage(req); AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo( - this.getTenantIdentifierWithStorageFromRequest( - req), doUnionOfAccountInfo, email, phoneNumber, thirdPartyId, thirdPartyUserId); - UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorage, users); + tenantIdentifier, storage, doUnionOfAccountInfo, email, phoneNumber, thirdPartyId, + thirdPartyUserId); + UserIdMapping.populateExternalUserIdForUsers(storage, users); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/core/NotFoundOrHelloAPI.java b/src/main/java/io/supertokens/webserver/api/core/NotFoundOrHelloAPI.java index ed3806e42..ab86ac62c 100644 --- a/src/main/java/io/supertokens/webserver/api/core/NotFoundOrHelloAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/NotFoundOrHelloAPI.java @@ -20,8 +20,9 @@ import io.supertokens.output.Logging; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.RateLimiter; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -71,10 +72,12 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO protected void handleRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // getServletPath returns the path without the base path. - AppIdentifierWithStorage appIdentifierWithStorage = null; + AppIdentifier appIdentifier; + Storage[] storages; try { - appIdentifierWithStorage = getAppIdentifierWithStorage(req); + appIdentifier = getAppIdentifier(req); + storages = StorageLayer.getStoragesForApp(main, appIdentifier); } catch (TenantOrAppNotFoundException e) { // we send 500 status code throw new ServletException(e); @@ -83,7 +86,7 @@ protected void handleRequest(HttpServletRequest req, HttpServletResponse resp) t if (req.getServletPath().equals("/")) { // API is app specific try { - RateLimiter rateLimiter = RateLimiter.getInstance(getAppIdentifierWithStorage(req), super.main, 200); + RateLimiter rateLimiter = RateLimiter.getInstance(appIdentifier, super.main, 200); if (!rateLimiter.checkRequest()) { if (Main.isTesting) { super.sendTextResponse(200, "RateLimitedHello", resp); @@ -93,21 +96,22 @@ protected void handleRequest(HttpServletRequest req, HttpServletResponse resp) t return; } - for (Storage storage : appIdentifierWithStorage.getStorages()) { + for (Storage storage : storages) { // even if the public tenant does not exist, the following function will return a null // idea here is to test that the storage is working - storage.getKeyValue(appIdentifierWithStorage.getAsPublicTenantIdentifier(), "Test"); + storage.getKeyValue(appIdentifier.getAsPublicTenantIdentifier(), "Test"); } super.sendTextResponse(200, "Hello", resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException e) { // we send 500 status code throw new ServletException(e); } } else { super.sendTextResponse(404, "Not found", resp); - Logging.error(main, appIdentifierWithStorage.getAsPublicTenantIdentifier(), "Unknown API called: " + req.getRequestURL(), false); + Logging.error(main, appIdentifier.getAsPublicTenantIdentifier(), "Unknown API called: " + req.getRequestURL(), + false); } } diff --git a/src/main/java/io/supertokens/webserver/api/core/RequestStatsAPI.java b/src/main/java/io/supertokens/webserver/api/core/RequestStatsAPI.java index 2cde53961..034c71eb5 100644 --- a/src/main/java/io/supertokens/webserver/api/core/RequestStatsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/RequestStatsAPI.java @@ -18,19 +18,15 @@ import com.google.gson.JsonObject; import io.supertokens.Main; -import io.supertokens.cliOptions.CLIOptions; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.webserver.InputParser; import io.supertokens.webserver.RequestStats; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.io.File; import java.io.IOException; public class RequestStatsAPI extends WebserverAPI { @@ -49,7 +45,8 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific try { - AppIdentifier appIdentifier = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + AppIdentifier appIdentifier = getAppIdentifier(req); + enforcePublicTenantAndGetPublicTenantStorage(req); // enforce public tenant JsonObject stats = RequestStats.getInstance(main, appIdentifier).getStats(); stats.addProperty("status", "OK"); super.sendJsonResponse(200, stats, resp); diff --git a/src/main/java/io/supertokens/webserver/api/core/TelemetryAPI.java b/src/main/java/io/supertokens/webserver/api/core/TelemetryAPI.java index c42460755..5a35ec937 100644 --- a/src/main/java/io/supertokens/webserver/api/core/TelemetryAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/TelemetryAPI.java @@ -46,8 +46,8 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific try { - KeyValueInfo telemetryId = Telemetry.getTelemetryId(main, - this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req)); + this.enforcePublicTenantAndGetPublicTenantStorage(req); // enforce public tenant + KeyValueInfo telemetryId = Telemetry.getTelemetryId(main, getAppIdentifier(req)); JsonObject result = new JsonObject(); if (telemetryId == null) { diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index 4e7e2120c..2959ca24a 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -25,10 +25,11 @@ import io.supertokens.authRecipe.UserPaginationToken; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.utils.SemVer; @@ -41,7 +42,6 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.stream.Stream; public class UsersAPI extends WebserverAPI { @@ -75,9 +75,11 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } } - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier = null; + Storage storage = null; try { - tenantIdentifierWithStorage = this.getTenantIdentifierWithStorageFromRequest(req); + tenantIdentifier = getTenantIdentifier(req); + storage = this.getTenantStorage(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } @@ -164,11 +166,11 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - UserPaginationContainer users = AuthRecipe.getUsers(tenantIdentifierWithStorage, + UserPaginationContainer users = AuthRecipe.getUsers(tenantIdentifier, storage, limit, timeJoinedOrder, paginationToken, recipeIdsEnumBuilder.build().toArray(RECIPE_ID[]::new), searchTags); - UserIdMapping.populateExternalUserIdForUsers(tenantIdentifierWithStorage, users.users); + UserIdMapping.populateExternalUserIdForUsers(storage, users.users); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -199,7 +201,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } super.sendJsonResponse(200, result, resp); } catch (UserPaginationToken.InvalidTokenException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); throw new ServletException(new BadRequestException("invalid pagination token")); } catch (StorageQueryException | TenantOrAppNotFoundException e) { throw new ServletException(e); diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersCountAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersCountAPI.java index 686676776..7719d43b3 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersCountAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersCountAPI.java @@ -21,8 +21,10 @@ import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -76,13 +78,17 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO long count; if (includeAllTenants) { - AppIdentifierWithStorage appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage[] storages = enforcePublicTenantAndGetAllStoragesForApp(req); - count = AuthRecipe.getUsersCountAcrossAllTenants(appIdentifierWithStorage, + count = AuthRecipe.getUsersCountAcrossAllTenants(appIdentifier, storages, recipeIdsEnumBuilder.build().toArray(RECIPE_ID[]::new)); } else { - count = AuthRecipe.getUsersCountForTenant(this.getTenantIdentifierWithStorageFromRequest(req), + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); + + count = AuthRecipe.getUsersCountForTenant(tenantIdentifier, storage, recipeIdsEnumBuilder.build().toArray(RECIPE_ID[]::new)); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java index f7296a2a5..f8032715c 100644 --- a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardSignInAPI.java @@ -61,7 +61,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { String sessionId = Dashboard.signInDashboardUser( - super.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req), main, email, password); + getAppIdentifier(req), + enforcePublicTenantAndGetPublicTenantStorage(req), main, email, password); if (sessionId == null) { JsonObject response = new JsonObject(); response.addProperty("status", "INVALID_CREDENTIALS_ERROR"); diff --git a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardUserAPI.java b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardUserAPI.java index 51eb3618e..722fb4245 100644 --- a/src/main/java/io/supertokens/webserver/api/dashboard/DashboardUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/dashboard/DashboardUserAPI.java @@ -24,12 +24,13 @@ import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.dashboard.DashboardUser; import io.supertokens.pluginInterface.dashboard.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.dashboard.exceptions.UserIdNotFoundException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.Utils; @@ -91,7 +92,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } DashboardUser user = Dashboard.signUpDashboardUser( - this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req), + getAppIdentifier(req), + enforcePublicTenantAndGetPublicTenantStorage(req), main, email, password); JsonObject userAsJsonObject = new JsonParser().parse(new Gson().toJson(user)).getAsJsonObject(); @@ -146,16 +148,15 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - AppIdentifierWithStorage appIdentifierWithStorage = - this.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant( - req); + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req); String userId = InputParser.parseStringOrThrowError(input, "userId", true); if (userId != null) { // normalize userId userId = Utils.normalizeAndValidateStringParam(userId, "userId"); // retrieve updated user details - DashboardUser user = Dashboard.updateUsersCredentialsWithUserId(appIdentifierWithStorage, + DashboardUser user = Dashboard.updateUsersCredentialsWithUserId(appIdentifier, storage, main, userId, newEmail, newPassword); JsonObject userJsonObject = new JsonParser().parse(new Gson().toJson(user)) .getAsJsonObject(); @@ -173,14 +174,14 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO email = io.supertokens.utils.Utils.normaliseEmail(email); // check if the user exists - DashboardUser user = Dashboard.getDashboardUserByEmail(appIdentifierWithStorage, email); + DashboardUser user = Dashboard.getDashboardUserByEmail(appIdentifier, storage, email); if (user == null) { throw new UserIdNotFoundException(); } // retrieve updated user details - DashboardUser updatedUser = Dashboard.updateUsersCredentialsWithUserId(appIdentifierWithStorage, - main, user.userId, newEmail, newPassword); + DashboardUser updatedUser = Dashboard.updateUsersCredentialsWithUserId(appIdentifier, + storage, main, user.userId, newEmail, newPassword); JsonObject userJsonObject = new JsonParser().parse(new Gson().toJson(updatedUser)).getAsJsonObject(); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); @@ -216,7 +217,8 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws // normalize userId userId = Utils.normalizeAndValidateStringParam(userId, "userId"); boolean didUserExist = Dashboard.deleteUserWithUserId( - super.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req), userId); + getAppIdentifier(req), + enforcePublicTenantAndGetPublicTenantStorage(req), userId); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("didUserExist", didUserExist); @@ -232,7 +234,8 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws email = io.supertokens.utils.Utils.normaliseEmail(email); boolean didUserExist = Dashboard.deleteUserWithEmail( - super.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req), email); + getAppIdentifier(req), + enforcePublicTenantAndGetPublicTenantStorage(req), email); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("didUserExist", didUserExist); diff --git a/src/main/java/io/supertokens/webserver/api/dashboard/GetDashboardSessionsForUserAPI.java b/src/main/java/io/supertokens/webserver/api/dashboard/GetDashboardSessionsForUserAPI.java index 395e28337..def40877c 100644 --- a/src/main/java/io/supertokens/webserver/api/dashboard/GetDashboardSessionsForUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/dashboard/GetDashboardSessionsForUserAPI.java @@ -56,7 +56,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonArray arr = new com.google.gson.JsonParser().parse(new Gson().toJson( Dashboard.getAllDashboardSessionsForUser( - super.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req), + getAppIdentifier(req), + enforcePublicTenantAndGetPublicTenantStorage(req), userId))).getAsJsonArray(); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/dashboard/GetDashboardUsersAPI.java b/src/main/java/io/supertokens/webserver/api/dashboard/GetDashboardUsersAPI.java index 6bbd5b817..8c4333c02 100644 --- a/src/main/java/io/supertokens/webserver/api/dashboard/GetDashboardUsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/dashboard/GetDashboardUsersAPI.java @@ -51,7 +51,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonArray arr = new com.google.gson.JsonParser().parse(new Gson().toJson( Dashboard.getAllDashboardUsers( - super.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req), main))) + getAppIdentifier(req), + enforcePublicTenantAndGetPublicTenantStorage(req), main))) .getAsJsonArray(); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/dashboard/RevokeSessionAPI.java b/src/main/java/io/supertokens/webserver/api/dashboard/RevokeSessionAPI.java index 20ad008ce..dcbb31918 100644 --- a/src/main/java/io/supertokens/webserver/api/dashboard/RevokeSessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/dashboard/RevokeSessionAPI.java @@ -53,7 +53,8 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws try { Dashboard.revokeSessionWithSessionId( - super.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req), sessionId); + getAppIdentifier(req), + enforcePublicTenantAndGetPublicTenantStorage(req), sessionId); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); diff --git a/src/main/java/io/supertokens/webserver/api/dashboard/VerifyDashboardUserSessionAPI.java b/src/main/java/io/supertokens/webserver/api/dashboard/VerifyDashboardUserSessionAPI.java index 917ea7877..9e0fd4287 100644 --- a/src/main/java/io/supertokens/webserver/api/dashboard/VerifyDashboardUserSessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/dashboard/VerifyDashboardUserSessionAPI.java @@ -22,8 +22,9 @@ import io.supertokens.dashboard.exceptions.UserSuspendedException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.SemVer; import io.supertokens.webserver.InputParser; @@ -61,10 +62,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { JsonObject invalidSessionResp = new JsonObject(); invalidSessionResp.addProperty("status", "INVALID_SESSION_ERROR"); - AppIdentifierWithStorage identifierWithStorage = super.getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage = super.enforcePublicTenantAndGetPublicTenantStorage(req); - if (Dashboard.isValidUserSession(identifierWithStorage, main, sessionId)) { - String email = Dashboard.getEmailFromSessionId(identifierWithStorage, main, sessionId); + if (Dashboard.isValidUserSession(appIdentifier, storage, main, sessionId)) { + String email = Dashboard.getEmailFromSessionId(appIdentifier, storage, sessionId); if (email == null) { super.sendJsonResponse(200, invalidSessionResp, resp); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/ConsumeResetPasswordAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/ConsumeResetPasswordAPI.java index 362ce9020..671d45469 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/ConsumeResetPasswordAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/ConsumeResetPasswordAPI.java @@ -22,9 +22,10 @@ import io.supertokens.emailpassword.exceptions.ResetPasswordInvalidTokenException; import io.supertokens.output.Logging; 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.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -57,19 +58,21 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I String token = InputParser.parseStringOrThrowError(input, "token", false); assert token != null; - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier = null; + Storage storage = null; try { - tenantIdentifierWithStorage = getTenantIdentifierWithStorageFromRequest(req); + tenantIdentifier = getTenantIdentifier(req); + storage = getTenantStorage(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { EmailPassword.ConsumeResetPasswordTokenResult result = EmailPassword.consumeResetPasswordToken( - tenantIdentifierWithStorage, token); + tenantIdentifier, storage, token); io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping.getUserIdMapping( - tenantIdentifierWithStorage.toAppIdentifierWithStorage(), result.userId, UserIdType.SUPERTOKENS); + tenantIdentifier.toAppIdentifier(), storage, result.userId, UserIdType.SUPERTOKENS); // if userIdMapping exists, pass the externalUserId to the response if (userIdMapping != null) { @@ -84,7 +87,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I super.sendJsonResponse(200, resultJson, resp); } catch (ResetPasswordInvalidTokenException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject result = new JsonObject(); result.addProperty("status", "RESET_PASSWORD_INVALID_TOKEN_ERROR"); super.sendJsonResponse(200, result, resp); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/GeneratePasswordResetTokenAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/GeneratePasswordResetTokenAPI.java index 24f55a679..772bcaa70 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/GeneratePasswordResetTokenAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/GeneratePasswordResetTokenAPI.java @@ -18,11 +18,12 @@ import com.google.gson.JsonObject; import io.supertokens.Main; -import io.supertokens.TenantIdentifierWithStorageAndUserIdMapping; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; @@ -60,29 +61,29 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I String userId = InputParser.parseStringOrThrowError(input, "userId", false); // logic according to https://github.com/supertokens/supertokens-core/issues/106 - TenantIdentifier tenantIdentifier = null; + TenantIdentifier tenantIdentifier; try { - tenantIdentifier = getTenantIdentifierWithStorageFromRequest(req); + tenantIdentifier = getTenantIdentifier(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { - TenantIdentifierWithStorageAndUserIdMapping tenantIdentifierStorageAndMapping = - getTenantIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); + StorageAndUserIdMapping storageAndUserIdMapping = + getStorageAndUserIdMappingForTenantSpecificApi(req, userId, UserIdType.ANY); // if a userIdMapping exists, pass the superTokensUserId to the generatePasswordResetToken - if (tenantIdentifierStorageAndMapping.userIdMapping != null) { - userId = tenantIdentifierStorageAndMapping.userIdMapping.superTokensUserId; + if (storageAndUserIdMapping.userIdMapping != null) { + userId = storageAndUserIdMapping.userIdMapping.superTokensUserId; } String token; if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { String email = InputParser.parseStringOrThrowError(input, "email", false); token = EmailPassword.generatePasswordResetToken( - tenantIdentifierStorageAndMapping.tenantIdentifierWithStorage, super.main, userId, email); + tenantIdentifier, storageAndUserIdMapping.storage, super.main, userId, email); } else { token = EmailPassword.generatePasswordResetTokenBeforeCdi4_0( - tenantIdentifierStorageAndMapping.tenantIdentifierWithStorage, super.main, userId); + tenantIdentifier, storageAndUserIdMapping.storage, super.main, userId); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java index f1d9a7a9a..3d05fd5ff 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java @@ -23,10 +23,11 @@ import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.utils.SemVer; @@ -94,11 +95,12 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); EmailPassword.ImportUserResponse importUserResponse = EmailPassword.importUserWithPasswordHash( - tenant, main, email, + tenantIdentifier, storage, main, email, passwordHash, passwordHashingAlgorithm); - UserIdMapping.populateExternalUserIdForUsers(getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{importUserResponse.user}); + UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{importUserResponse.user}); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); JsonObject userJson = diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/ResetPasswordAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/ResetPasswordAPI.java index 05d354676..d52c3c884 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/ResetPasswordAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/ResetPasswordAPI.java @@ -22,9 +22,10 @@ import io.supertokens.emailpassword.exceptions.ResetPasswordInvalidTokenException; import io.supertokens.output.Logging; 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.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -73,18 +74,20 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I throw new ServletException(new WebserverAPI.BadRequestException("Password cannot be an empty string")); } - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - tenantIdentifierWithStorage = getTenantIdentifierWithStorageFromRequest(req); + tenantIdentifier = getTenantIdentifier(req); + storage = getTenantStorage(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { - String userId = EmailPassword.resetPassword(tenantIdentifierWithStorage, super.main, token, newPassword); + String userId = EmailPassword.resetPassword(tenantIdentifier, storage, super.main, token, newPassword); io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping.getUserIdMapping( - tenantIdentifierWithStorage.toAppIdentifierWithStorage(), userId, UserIdType.SUPERTOKENS); + tenantIdentifier.toAppIdentifier(), storage, userId, UserIdType.SUPERTOKENS); // if userIdMapping exists, pass the externalUserId to the response if (userIdMapping != null) { @@ -106,7 +109,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I super.sendJsonResponse(200, result, resp); } catch (ResetPasswordInvalidTokenException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject result = new JsonObject(); result.addProperty("status", "RESET_PASSWORD_INVALID_TOKEN_ERROR"); super.sendJsonResponse(200, result, resp); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java index 993950ea2..32272a8b2 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java @@ -25,10 +25,11 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; @@ -66,19 +67,22 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I String normalisedEmail = Utils.normaliseEmail(email); - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - tenantIdentifierWithStorage = getTenantIdentifierWithStorageFromRequest(req); + tenantIdentifier = getTenantIdentifier(req); + storage = getTenantStorage(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { - AuthRecipeUserInfo user = EmailPassword.signIn(tenantIdentifierWithStorage, super.main, normalisedEmail, + AuthRecipeUserInfo user = EmailPassword.signIn(tenantIdentifier, storage, super.main, normalisedEmail, password); - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(tenantIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, + new AuthRecipeUserInfo[]{user}); - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, + ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, user.getSupertokensUserId()); // use the internal user id JsonObject result = new JsonObject(); @@ -101,7 +105,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I super.sendJsonResponse(200, result, resp); } catch (WrongCredentialsException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject result = new JsonObject(); result.addProperty("status", "WRONG_CREDENTIALS_ERROR"); super.sendJsonResponse(200, result, resp); 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..01217f817 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java @@ -24,11 +24,11 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; @@ -70,18 +70,21 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I throw new ServletException(new WebserverAPI.BadRequestException("Password cannot be an empty string")); } - TenantIdentifier tenantIdentifier = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - tenantIdentifier = getTenantIdentifierWithStorageFromRequest(req); + tenantIdentifier = getTenantIdentifier(req); + storage = getTenantStorage(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { - TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); - AuthRecipeUserInfo user = EmailPassword.signUp(tenant, super.main, normalisedEmail, password); + AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifier, storage, super.main, normalisedEmail, + password); - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, user.getSupertokensUserId()); + ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, + user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index 7f992f6a5..f7b67e729 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -17,19 +17,21 @@ package io.supertokens.webserver.api.emailpassword; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -76,35 +78,38 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO try { // API is app specific for get by UserId AuthRecipeUserInfo user = null; + AppIdentifier appIdentifier = getAppIdentifier(req); try { if (userId != null) { // Query by userId - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(req, userId, + UserIdType.ANY, true); // if a userIdMapping exists, pass the superTokensUserId to the getUserUsingId function - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - userId = appIdentifierWithStorageAndUserIdMapping.userIdMapping.superTokensUserId; + if (storageAndUserIdMapping.userIdMapping != null) { + userId = storageAndUserIdMapping.userIdMapping.superTokensUserId; } user = EmailPassword.getUserUsingId( - appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId); + appIdentifier, + storageAndUserIdMapping.storage, userId); if (user != null) { - UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); + UserIdMapping.populateExternalUserIdForUsers(storageAndUserIdMapping.storage, + new AuthRecipeUserInfo[]{user}); } } else { // API is tenant specific for get by Email // Query by email String normalisedEmail = Utils.normaliseEmail(email); - TenantIdentifierWithStorage tenantIdentifierWithStorage = - this.getTenantIdentifierWithStorageFromRequest( - req); - user = EmailPassword.getUserUsingEmail(tenantIdentifierWithStorage, normalisedEmail); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = this.getTenantStorage(req); + user = EmailPassword.getUserUsingEmail(tenantIdentifier, storage, normalisedEmail); // if a userIdMapping exists, set the userId in the response to the externalUserId if (user != null) { - UserIdMapping.populateExternalUserIdForUsers(tenantIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); + UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{user}); } } } catch (UnknownUserIdException e) { @@ -131,7 +136,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO super.sendJsonResponse(200, result, resp); } - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } @@ -160,28 +165,31 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO email = Utils.normaliseEmail(email); AppIdentifier appIdentifier = null; try { - appIdentifier = this.getAppIdentifierWithStorage(req); + appIdentifier = this.getAppIdentifier(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(req, userId, + UserIdType.ANY, true); // if a userIdMapping exists, pass the superTokensUserId to the updateUsersEmailOrPassword - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - userId = appIdentifierWithStorageAndUserIdMapping.userIdMapping.superTokensUserId; + if (storageAndUserIdMapping.userIdMapping != null) { + userId = storageAndUserIdMapping.userIdMapping.superTokensUserId; } EmailPassword.updateUsersEmailOrPassword( - appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, + appIdentifier, + storageAndUserIdMapping.storage, main, userId, email, password); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | + BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { diff --git a/src/main/java/io/supertokens/webserver/api/emailverification/GenerateEmailVerificationTokenAPI.java b/src/main/java/io/supertokens/webserver/api/emailverification/GenerateEmailVerificationTokenAPI.java index 4d4ccdefb..e2256f99a 100644 --- a/src/main/java/io/supertokens/webserver/api/emailverification/GenerateEmailVerificationTokenAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailverification/GenerateEmailVerificationTokenAPI.java @@ -20,7 +20,8 @@ import io.supertokens.Main; import io.supertokens.emailverification.EmailVerification; import io.supertokens.emailverification.exception.EmailAlreadyVerifiedException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; @@ -59,9 +60,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I assert email != null; email = Utils.normaliseEmail(email); - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - tenantIdentifierWithStorage = getTenantIdentifierWithStorageFromRequest(req); + tenantIdentifier = getTenantIdentifier(req); + storage = getTenantStorage(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } @@ -70,7 +73,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I // but then changed slightly when extracting this into its own recipe try { - String token = EmailVerification.generateEmailVerificationToken(tenantIdentifierWithStorage, super.main, + String token = EmailVerification.generateEmailVerificationToken(tenantIdentifier, storage, super.main, userId, email); JsonObject result = new JsonObject(); @@ -78,7 +81,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I result.addProperty("token", token); super.sendJsonResponse(200, result, resp); } catch (EmailAlreadyVerifiedException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject result = new JsonObject(); result.addProperty("status", "EMAIL_ALREADY_VERIFIED_ERROR"); super.sendJsonResponse(200, result, resp); diff --git a/src/main/java/io/supertokens/webserver/api/emailverification/RevokeAllTokensForUserAPI.java b/src/main/java/io/supertokens/webserver/api/emailverification/RevokeAllTokensForUserAPI.java index b91fa45b9..d61824d06 100644 --- a/src/main/java/io/supertokens/webserver/api/emailverification/RevokeAllTokensForUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailverification/RevokeAllTokensForUserAPI.java @@ -52,7 +52,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I email = Utils.normaliseEmail(email); try { - EmailVerification.revokeAllTokens(this.getTenantIdentifierWithStorageFromRequest(req), userId, email); + EmailVerification.revokeAllTokens(getTenantIdentifier(req), getTenantStorage(req), userId, email); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/emailverification/UnverifyEmailAPI.java b/src/main/java/io/supertokens/webserver/api/emailverification/UnverifyEmailAPI.java index cc3f0931b..826805856 100644 --- a/src/main/java/io/supertokens/webserver/api/emailverification/UnverifyEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailverification/UnverifyEmailAPI.java @@ -18,10 +18,16 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.emailverification.EmailVerification; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -52,13 +58,22 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I email = Utils.normaliseEmail(email); try { - EmailVerification.unverifyEmail(this.getAppIdentifierWithStorage(req), userId, email); + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage; + try { + StorageAndUserIdMapping storageAndUidMapping = enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + storage = storageAndUidMapping.storage; + } catch (UnknownUserIdException e) { + throw new IllegalStateException("should never happen"); + } + EmailVerification.unverifyEmail(appIdentifier, storage, userId, email); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/emailverification/VerifyEmailAPI.java b/src/main/java/io/supertokens/webserver/api/emailverification/VerifyEmailAPI.java index f0fd338ec..a7ba01f29 100644 --- a/src/main/java/io/supertokens/webserver/api/emailverification/VerifyEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailverification/VerifyEmailAPI.java @@ -18,15 +18,21 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.emailverification.EmailVerification; import io.supertokens.emailverification.User; import io.supertokens.emailverification.exception.EmailVerificationInvalidTokenException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -65,15 +71,17 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I throw new ServletException(new BadRequestException("Unsupported method for email verification")); } - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - tenantIdentifierWithStorage = this.getTenantIdentifierWithStorageFromRequest(req); + tenantIdentifier = getTenantIdentifier(req); + storage = this.getTenantStorage(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { - User user = EmailVerification.verifyEmail(tenantIdentifierWithStorage, token); + User user = EmailVerification.verifyEmail(tenantIdentifier, storage, token); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -82,7 +90,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I super.sendJsonResponse(200, result, resp); } catch (EmailVerificationInvalidTokenException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject result = new JsonObject(); result.addProperty("status", "EMAIL_VERIFICATION_INVALID_TOKEN_ERROR"); super.sendJsonResponse(200, result, resp); @@ -101,15 +109,23 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws Servl assert email != null; try { - boolean isVerified = EmailVerification.isEmailVerified(this.getAppIdentifierWithStorage(req), userId, - email); + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage; + try { + StorageAndUserIdMapping storageAndUserIdMapping = enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + storage = storageAndUserIdMapping.storage; + } catch (UnknownUserIdException e) { + throw new IllegalStateException("should never happen"); + } + boolean isVerified = EmailVerification.isEmailVerified(appIdentifier, storage, userId, email); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); result.addProperty("isVerified", isVerified); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } diff --git a/src/main/java/io/supertokens/webserver/api/jwt/JWKSAPI.java b/src/main/java/io/supertokens/webserver/api/jwt/JWKSAPI.java index c79d0f1d2..cd686c401 100644 --- a/src/main/java/io/supertokens/webserver/api/jwt/JWKSAPI.java +++ b/src/main/java/io/supertokens/webserver/api/jwt/JWKSAPI.java @@ -22,6 +22,7 @@ import com.google.gson.JsonParser; import io.supertokens.Main; import io.supertokens.jwt.exceptions.UnsupportedJWTSigningAlgorithmException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; @@ -54,13 +55,16 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific try { - List jwks = SigningKeys.getInstance(this.getAppIdentifierWithStorage(req), main).getJWKS(); + enforcePublicTenantAndGetPublicTenantStorage(req); + List jwks = SigningKeys.getInstance(getAppIdentifier(req), main).getJWKS(); JsonObject reply = new JsonObject(); JsonArray jwksJsonArray = new JsonParser().parse(new Gson().toJson(jwks)).getAsJsonArray(); reply.add("keys", jwksJsonArray); reply.addProperty("status", "OK"); super.sendJsonResponse(200, reply, resp); - } catch (StorageQueryException | UnsupportedJWTSigningAlgorithmException | StorageTransactionLogicException | NoSuchAlgorithmException | InvalidKeySpecException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | UnsupportedJWTSigningAlgorithmException | StorageTransactionLogicException | + NoSuchAlgorithmException | InvalidKeySpecException | TenantOrAppNotFoundException | + BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/jwt/JWTSigningAPI.java b/src/main/java/io/supertokens/webserver/api/jwt/JWTSigningAPI.java index 6c65f3bfd..778a68654 100644 --- a/src/main/java/io/supertokens/webserver/api/jwt/JWTSigningAPI.java +++ b/src/main/java/io/supertokens/webserver/api/jwt/JWTSigningAPI.java @@ -20,6 +20,7 @@ import io.supertokens.Main; import io.supertokens.jwt.JWTSigningFunctions; import io.supertokens.jwt.exceptions.UnsupportedJWTSigningAlgorithmException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; @@ -78,7 +79,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - String jwt = JWTSigningFunctions.createJWTToken(this.getAppIdentifierWithStorage(req), main, + this.enforcePublicTenantAndGetPublicTenantStorage(req); + String jwt = JWTSigningFunctions.createJWTToken(getAppIdentifier(req), main, algorithm.toUpperCase(), payload, jwksDomain, validity, useDynamicKey); JsonObject reply = new JsonObject(); @@ -89,7 +91,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject reply = new JsonObject(); reply.addProperty("status", UNSUPPORTED_ALGORITHM_ERROR_STATUS); super.sendJsonResponse(200, reply, resp); - } catch (StorageQueryException | StorageTransactionLogicException | NoSuchAlgorithmException | InvalidKeySpecException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | StorageTransactionLogicException | NoSuchAlgorithmException | + InvalidKeySpecException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java index 739d182bd..0fbaac29a 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java @@ -17,8 +17,8 @@ package io.supertokens.webserver.api.multitenancy; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithEmailAlreadyExistsException; @@ -28,6 +28,7 @@ import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; @@ -73,14 +74,16 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + + StorageAndUserIdMapping storageAndUserIdMapping = getStorageAndUserIdMappingForTenantSpecificApi( + req, userId, UserIdType.ANY); + if (storageAndUserIdMapping.userIdMapping != null) { + userId = storageAndUserIdMapping.userIdMapping.superTokensUserId; } boolean addedToTenant = Multitenancy.addUserIdToTenant(main, - getTenantIdentifierWithStorageFromRequest(req), userId); + tenantIdentifier, storageAndUserIdMapping.storage, userId); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateAppAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateAppAPI.java index c51e2f382..e58343a28 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateAppAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateAppAPI.java @@ -89,8 +89,9 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } TenantIdentifier sourceTenantIdentifier; + try { - sourceTenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req); + sourceTenantIdentifier = getTenantIdentifier(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateConnectionUriDomainAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateConnectionUriDomainAPI.java index c17643d29..2e2ff217c 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateConnectionUriDomainAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateConnectionUriDomainAPI.java @@ -90,7 +90,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO TenantIdentifier sourceTenantIdentifier; try { - sourceTenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req); + sourceTenantIdentifier = getTenantIdentifier(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateTenantOrGetTenantAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateTenantOrGetTenantAPI.java index d67a7b533..db8ab6079 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateTenantOrGetTenantAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/CreateOrUpdateTenantOrGetTenantAPI.java @@ -93,7 +93,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO TenantIdentifier sourceTenantIdentifier; try { - sourceTenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req); + sourceTenantIdentifier = getTenantIdentifier(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } @@ -109,13 +109,13 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { try { - TenantIdentifierWithStorage tenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifier); if (config == null) { throw new TenantOrAppNotFoundException(tenantIdentifier); } boolean shouldProtect = shouldProtectProtectedConfig(req); - JsonObject result = config.toJson(shouldProtect, tenantIdentifier.getStorage(), CoreConfig.PROTECTED_CONFIGS); + JsonObject result = config.toJson(shouldProtect, getTenantStorage(req), CoreConfig.PROTECTED_CONFIGS); result.addProperty("status", "OK"); if (getVersionFromRequest(req).lesserThan(SemVer.v5_0)) { diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java b/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java index a37c3a595..57ee9f173 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java @@ -17,13 +17,14 @@ package io.supertokens.webserver.api.multitenancy; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.SemVer; @@ -66,16 +67,18 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); String externalUserId = null; - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; - externalUserId = mappingAndStorage.userIdMapping.externalUserId; + + StorageAndUserIdMapping storageAndUserIdMapping = getStorageAndUserIdMappingForTenantSpecificApi( + req, userId, UserIdType.ANY); + if (storageAndUserIdMapping.userIdMapping != null) { + userId = storageAndUserIdMapping.userIdMapping.superTokensUserId; + externalUserId = storageAndUserIdMapping.userIdMapping.externalUserId; } boolean wasAssociated = Multitenancy.removeUserIdFromTenant(main, - getTenantIdentifierWithStorageFromRequest(req), userId, externalUserId); + tenantIdentifier, storageAndUserIdMapping.storage, userId, externalUserId); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -90,6 +93,5 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException e) { throw new ServletException(e); } - } } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/ListAppsAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/ListAppsAPI.java index 03b44525f..4b8166a5c 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/ListAppsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/ListAppsAPI.java @@ -23,9 +23,9 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.multitenancy.TenantConfig; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -52,18 +52,18 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - TenantIdentifierWithStorage tenantIdentifierWithStorage; try { - tenantIdentifierWithStorage = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = this.getTenantStorage(req); - if (!tenantIdentifierWithStorage.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID) - || !tenantIdentifierWithStorage.getAppId().equals(TenantIdentifier.DEFAULT_APP_ID)) { + if (!tenantIdentifier.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID) + || !tenantIdentifier.getAppId().equals(TenantIdentifier.DEFAULT_APP_ID)) { throw new BadPermissionException("Only the public tenantId and public appId is allowed to list " + "all apps associated with this connection uri domain"); } TenantConfig[] tenantConfigs = Multitenancy.getAllAppsAndTenantsForConnectionUriDomain( - tenantIdentifierWithStorage.getConnectionUriDomain(), main); + tenantIdentifier.getConnectionUriDomain(), main); Map> appsToTenants = new HashMap<>(); for (TenantConfig tenantConfig : tenantConfigs) { @@ -82,7 +82,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonArray tenantsArray = new JsonArray(); for (TenantConfig tenantConfig : entry.getValue()) { JsonObject tenantConfigJson = tenantConfig.toJson(shouldProtect, - tenantIdentifierWithStorage.getStorage(), CoreConfig.PROTECTED_CONFIGS); + storage, CoreConfig.PROTECTED_CONFIGS); tenantsArray.add(tenantConfigJson); } appObject.add("tenants", tenantsArray); diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/ListConnectionUriDomainsAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/ListConnectionUriDomainsAPI.java index 2aae0c19b..076880827 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/ListConnectionUriDomainsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/ListConnectionUriDomainsAPI.java @@ -23,9 +23,9 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.multitenancy.TenantConfig; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -52,11 +52,11 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - TenantIdentifierWithStorage tenantIdentifierWithStorage; try { - tenantIdentifierWithStorage = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = this.getTenantStorage(req); - if (!tenantIdentifierWithStorage.equals(new TenantIdentifier(null, null, null))) { + if (!tenantIdentifier.equals(new TenantIdentifier(null, null, null))) { throw new BadPermissionException( "Only the public tenantId, public appId and default connectionUriDomain is allowed to list all " + "connectionUriDomains and appIds associated with this " + @@ -95,7 +95,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonArray tenantsArray = new JsonArray(); for (TenantConfig tenantConfig : entry2.getValue()) { JsonObject tenantConfigJson = tenantConfig.toJson(shouldProtect, - tenantIdentifierWithStorage.getStorage(), CoreConfig.PROTECTED_CONFIGS); + storage, CoreConfig.PROTECTED_CONFIGS); tenantsArray.add(tenantConfigJson); } appObject.add("tenants", tenantsArray); diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/ListTenantsAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/ListTenantsAPI.java index 0000eb2b8..98fe89054 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/ListTenantsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/ListTenantsAPI.java @@ -23,9 +23,9 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.multitenancy.TenantConfig; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -48,22 +48,19 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - TenantIdentifierWithStorage tenantIdentifierWithStorage; try { - tenantIdentifierWithStorage = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); - if (!tenantIdentifierWithStorage.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID)) { - throw new BadPermissionException("Only the public tenantId is allowed to list all tenants " + - "associated with this app"); - } + enforcePublicTenantAndGetPublicTenantStorage(req); // enforce that this API is called using public tenant - TenantConfig[] tenantConfigs = Multitenancy.getAllTenantsForApp(tenantIdentifierWithStorage.toAppIdentifier(), main); + TenantConfig[] tenantConfigs = Multitenancy.getAllTenantsForApp(tenantIdentifier.toAppIdentifier(), main); JsonArray tenantsArray = new JsonArray(); boolean shouldProtect = shouldProtectProtectedConfig(req); for (TenantConfig tenantConfig : tenantConfigs) { JsonObject tenantConfigJson = tenantConfig.toJson(shouldProtect, - tenantIdentifierWithStorage.getStorage(), CoreConfig.PROTECTED_CONFIGS); + storage, CoreConfig.PROTECTED_CONFIGS); tenantsArray.add(tenantConfigJson); } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveAppAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveAppAPI.java index 1b6768d98..4219c5c79 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveAppAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveAppAPI.java @@ -59,7 +59,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - TenantIdentifier sourceTenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier sourceTenantIdentifier = this.getTenantIdentifier(req); if (!sourceTenantIdentifier.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID) || !sourceTenantIdentifier.getAppId().equals(TenantIdentifier.DEFAULT_APP_ID)) { throw new BadPermissionException("Only the public tenantId and public appId is allowed to delete an app"); diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveConnectionUriDomainAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveConnectionUriDomainAPI.java index 5097937ea..fa4ca3a68 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveConnectionUriDomainAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveConnectionUriDomainAPI.java @@ -58,7 +58,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - TenantIdentifier sourceTenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier sourceTenantIdentifier = this.getTenantIdentifier(req); if (!sourceTenantIdentifier.equals(new TenantIdentifier(null, null, null))) { throw new BadPermissionException( "Only the public tenantId, public appId and default connectionUriDomain is allowed to delete a connectionUriDomain"); diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveTenantAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveTenantAPI.java index 480b1cdd7..f1ae12b03 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveTenantAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/RemoveTenantAPI.java @@ -58,11 +58,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - TenantIdentifier sourceTenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier sourceTenantIdentifier = this.getTenantIdentifier(req); - if (!sourceTenantIdentifier.getTenantId().equals(TenantIdentifier.DEFAULT_TENANT_ID)) { - throw new BadPermissionException("Only the public tenantId is allowed to delete a tenant"); - } + enforcePublicTenantAndGetPublicTenantStorage(req); // Enforce public tenant boolean didExist = Multitenancy.deleteTenant(new TenantIdentifier(sourceTenantIdentifier.getConnectionUriDomain(), sourceTenantIdentifier.getAppId(), tenantId), main); diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/CreateOrUpdateThirdPartyConfigAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/CreateOrUpdateThirdPartyConfigAPI.java index 57f440390..a63a8a7c0 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/CreateOrUpdateThirdPartyConfigAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/CreateOrUpdateThirdPartyConfigAPI.java @@ -70,7 +70,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - TenantIdentifierWithStorage tenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); if (!tenantIdentifier.equals(TenantIdentifier.BASE_TENANT)) { if (Arrays.stream(FeatureFlag.getInstance(main, new AppIdentifier(null, null)).getEnabledFeatures()) diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/RemoveThirdPartyConfigAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/RemoveThirdPartyConfigAPI.java index 7c086b2ef..c92a514c0 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/RemoveThirdPartyConfigAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/RemoveThirdPartyConfigAPI.java @@ -26,7 +26,7 @@ import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.TenantConfig; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.ThirdPartyConfig; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.thirdparty.InvalidProviderConfigException; @@ -59,7 +59,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I thirdPartyId = thirdPartyId.trim(); try { - TenantIdentifierWithStorage tenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifier tenantIdentifier = this.getTenantIdentifier(req); + TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifier); if (config == null) { throw new TenantOrAppNotFoundException(tenantIdentifier); 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 404947121..cb9245d5a 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -19,16 +19,17 @@ import com.google.gson.JsonObject; import io.supertokens.ActiveUsers; import io.supertokens.Main; -import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.Passwordless.ConsumeCodeResponse; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; 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.utils.SemVer; import io.supertokens.webserver.InputParser; @@ -89,25 +90,24 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = this.getTenantStorage(req); ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode( - this.getTenantIdentifierWithStorageFromRequest(req), main, + tenantIdentifier, + storage, main, deviceId, deviceIdHash, userInputCode, linkCode, // From CDI version 4.0 onwards, the email verification will be set getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0), createRecipeUserIfNotExists); - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{consumeCodeResponse.user}); - - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, consumeCodeResponse.user.getSupertokensUserId()); - JsonObject result = new JsonObject(); result.addProperty("status", "OK"); if (consumeCodeResponse.user != null) { - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{consumeCodeResponse.user}); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{consumeCodeResponse.user}); - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, consumeCodeResponse.user.getSupertokensUserId()); + ActiveUsers.updateLastActive(this.getAppIdentifier(req), main, consumeCodeResponse.user.getSupertokensUserId()); JsonObject userJson = getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? consumeCodeResponse.user.toJson() : consumeCodeResponse.user.toJsonWithoutAccountLinking(); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/CreateCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/CreateCodeAPI.java index 96ee64990..7821a4477 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/CreateCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/CreateCodeAPI.java @@ -26,6 +26,7 @@ import io.supertokens.passwordless.exceptions.RestartFlowException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; import io.supertokens.utils.Utils; @@ -78,11 +79,13 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); CreateCodeResponse createCodeResponse = Passwordless.createCode( - this.getTenantIdentifierWithStorageFromRequest(req), main, email, + tenantIdentifier, + this.getTenantStorage(req), main, email, phoneNumber, deviceId, userInputCode); - long passwordlessCodeLifetime = Config.getConfig(this.getTenantIdentifierWithStorageFromRequest(req), main) + long passwordlessCodeLifetime = Config.getConfig(tenantIdentifier, main) .getPasswordlessCodeLifetime(); JsonObject result = new JsonObject(); 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 78b23ca3c..ea3b76fce 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodeAPI.java @@ -53,7 +53,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I String codeId = InputParser.parseStringOrThrowError(input, "codeId", false); try { - Passwordless.removeCode(this.getTenantIdentifierWithStorageFromRequest(req), codeId); + Passwordless.removeCode(getTenantIdentifier(req), getTenantStorage(req), codeId); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodesAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodesAPI.java index 476e4b562..0af938ab5 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodesAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/DeleteCodesAPI.java @@ -18,6 +18,7 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.RECIPE_ID; @@ -62,11 +63,12 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I throw new ServletException(new BadRequestException("Please provide exactly one of email or phoneNumber")); } try { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); if (email != null) { email = Utils.normaliseEmail(email); - Passwordless.removeCodesByEmail(this.getTenantIdentifierWithStorageFromRequest(req), email); + Passwordless.removeCodesByEmail(tenantIdentifier, getTenantStorage(req), email); } else { - Passwordless.removeCodesByPhoneNumber(this.getTenantIdentifierWithStorageFromRequest(req), phoneNumber); + Passwordless.removeCodesByPhoneNumber(tenantIdentifier, getTenantStorage(req), phoneNumber); } } catch (StorageTransactionLogicException | StorageQueryException | TenantOrAppNotFoundException e) { throw new ServletException(e); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/GetCodesAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/GetCodesAPI.java index 4a318195c..48ff84fa0 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/GetCodesAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/GetCodesAPI.java @@ -20,6 +20,8 @@ import com.google.gson.JsonObject; import io.supertokens.Main; import io.supertokens.config.Config; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.Passwordless.DeviceWithCodes; @@ -70,25 +72,29 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - long passwordlessCodeLifetime = Config.getConfig(this.getTenantIdentifierWithStorageFromRequest(req), main) + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); + + long passwordlessCodeLifetime = Config.getConfig(tenantIdentifier, main) .getPasswordlessCodeLifetime(); List devicesInfos; if (deviceId != null) { - DeviceWithCodes deviceWithCodes = Passwordless.getDeviceWithCodesById(this.getTenantIdentifierWithStorageFromRequest(req), - deviceId); + DeviceWithCodes deviceWithCodes = Passwordless.getDeviceWithCodesById( + tenantIdentifier, storage, deviceId); devicesInfos = deviceWithCodes == null ? Collections.emptyList() : Collections.singletonList(deviceWithCodes); } else if (deviceIdHash != null) { DeviceWithCodes deviceWithCodes = Passwordless.getDeviceWithCodesByIdHash( - this.getTenantIdentifierWithStorageFromRequest(req), deviceIdHash); + tenantIdentifier, storage, deviceIdHash); devicesInfos = deviceWithCodes == null ? Collections.emptyList() : Collections.singletonList(deviceWithCodes); } else if (email != null) { email = Utils.normaliseEmail(email); - devicesInfos = Passwordless.getDevicesWithCodesByEmail(this.getTenantIdentifierWithStorageFromRequest(req), email); + devicesInfos = Passwordless.getDevicesWithCodesByEmail( + tenantIdentifier, storage, email); } else { - devicesInfos = Passwordless.getDevicesWithCodesByPhoneNumber(this.getTenantIdentifierWithStorageFromRequest(req), - phoneNumber); + devicesInfos = Passwordless.getDevicesWithCodesByPhoneNumber( + tenantIdentifier, storage, phoneNumber); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index c4f3854f3..430743262 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -17,18 +17,22 @@ package io.supertokens.webserver.api.passwordless; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.Passwordless.FieldUpdate; import io.supertokens.passwordless.exceptions.PhoneNumberChangeNotAllowedException; import io.supertokens.passwordless.exceptions.UserWithoutContactInfoException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; import io.supertokens.useridmapping.UserIdType; @@ -76,32 +80,40 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO AuthRecipeUserInfo user; if (userId != null) { try { - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - userId = appIdentifierWithStorageAndUserIdMapping.userIdMapping.superTokensUserId; + AppIdentifier appIdentifier = getAppIdentifier(req); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(req, userId, + UserIdType.ANY, true); + if (storageAndUserIdMapping.userIdMapping != null) { + userId = storageAndUserIdMapping.userIdMapping.superTokensUserId; } - user = Passwordless.getUserById(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, - userId); + user = Passwordless.getUserById(appIdentifier, storageAndUserIdMapping.storage, userId); // if the userIdMapping exists set the userId in the response to the externalUserId if (user != null) { - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers( + storageAndUserIdMapping.storage, new AuthRecipeUserInfo[]{user}); } } catch (UnknownUserIdException e) { user = null; } } else if (email != null) { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); email = Utils.normaliseEmail(email); - user = Passwordless.getUserByEmail(this.getTenantIdentifierWithStorageFromRequest(req), email); + user = Passwordless.getUserByEmail(tenantIdentifier, storage, email); if (user != null) { - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{user}); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, + new AuthRecipeUserInfo[]{user}); } } else { - user = Passwordless.getUserByPhoneNumber(this.getTenantIdentifierWithStorageFromRequest(req), - phoneNumber); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); + + user = Passwordless.getUserByPhoneNumber(tenantIdentifier, storage, phoneNumber); if (user != null) { - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{user}); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, + new AuthRecipeUserInfo[]{user}); } } @@ -125,7 +137,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO result.add("user", userJson); super.sendJsonResponse(200, result, resp); } - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } @@ -152,20 +164,22 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO : Utils.normalizeIfPhoneNumber(InputParser.parseStringOrThrowError(input, "phoneNumber", false))); try { - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); + AppIdentifier appIdentifier = getAppIdentifier(req); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(req, userId, + UserIdType.ANY, true); // if a userIdMapping exists, pass the superTokensUserId to the updateUser - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - userId = appIdentifierWithStorageAndUserIdMapping.userIdMapping.superTokensUserId; + if (storageAndUserIdMapping.userIdMapping != null) { + userId = storageAndUserIdMapping.userIdMapping.superTokensUserId; } - Passwordless.updateUser(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, + Passwordless.updateUser(appIdentifier, storageAndUserIdMapping.storage, userId, emailUpdate, phoneNumberUpdate); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/session/HandshakeAPI.java b/src/main/java/io/supertokens/webserver/api/session/HandshakeAPI.java index 283b18e5a..f0b177292 100644 --- a/src/main/java/io/supertokens/webserver/api/session/HandshakeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/HandshakeAPI.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.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; @@ -57,17 +58,18 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - Utils.addLegacySigningKeyInfos(this.getAppIdentifierWithStorage(req), main, result, + Utils.addLegacySigningKeyInfos(this.getAppIdentifier(req), main, result, super.getVersionFromRequest(req).betweenInclusive(SemVer.v2_9, SemVer.v2_21)); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); result.addProperty("accessTokenBlacklistingEnabled", - Config.getConfig(this.getTenantIdentifierWithStorageFromRequest(req), main) + Config.getConfig(tenantIdentifier, main) .getAccessTokenBlacklisting()); result.addProperty("accessTokenValidity", - Config.getConfig(this.getTenantIdentifierWithStorageFromRequest(req), main) + Config.getConfig(tenantIdentifier, main) .getAccessTokenValidity()); result.addProperty("refreshTokenValidity", - Config.getConfig(this.getTenantIdentifierWithStorageFromRequest(req), main) + Config.getConfig(tenantIdentifier, main) .getRefreshTokenValidity()); super.sendJsonResponse(200, result, resp); } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | UnsupportedJWTSigningAlgorithmException e) { diff --git a/src/main/java/io/supertokens/webserver/api/session/JWTDataAPI.java b/src/main/java/io/supertokens/webserver/api/session/JWTDataAPI.java index 22995811c..c87c1eeb5 100644 --- a/src/main/java/io/supertokens/webserver/api/session/JWTDataAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/JWTDataAPI.java @@ -23,10 +23,10 @@ import io.supertokens.exceptions.UnauthorisedException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.session.Session; import io.supertokens.session.accessToken.AccessToken; @@ -64,11 +64,12 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject userDataInJWT = InputParser.parseJsonObjectOrThrowError(input, "userDataInJWT", false); assert userDataInJWT != null; - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - AppIdentifierWithStorage appIdentifier = getAppIdentifierWithStorage(req); - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); - tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + AppIdentifier appIdentifier = getAppIdentifier(req); + tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); + storage = StorageLayer.getStorage(tenantIdentifier, main); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } @@ -76,10 +77,10 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO try { if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v2_21)) { AccessToken.VERSION version = AccessToken.getAccessTokenVersionForCDI(getVersionFromRequest(req)); - Session.updateSession(tenantIdentifierWithStorage, sessionHandle, null, + Session.updateSession(tenantIdentifier, storage, sessionHandle, null, userDataInJWT, version); } else { - Session.updateSessionBeforeCDI2_21(tenantIdentifierWithStorage, sessionHandle, + Session.updateSessionBeforeCDI2_21(tenantIdentifier, storage, sessionHandle, null, userDataInJWT); } @@ -93,7 +94,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } catch (AccessTokenPayloadError e) { throw new ServletException(new BadRequestException(e.getMessage())); } catch (UnauthorisedException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "UNAUTHORISED"); reply.addProperty("message", e.getMessage()); @@ -108,17 +109,18 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO String sessionHandle = InputParser.getQueryParamOrThrowError(req, "sessionHandle", false); assert sessionHandle != null; - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - AppIdentifierWithStorage appIdentifier = getAppIdentifierWithStorage(req); - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); - tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + AppIdentifier appIdentifier = getAppIdentifier(req); + tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); + storage = StorageLayer.getStorage(tenantIdentifier, main); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { - JsonElement jwtPayload = Session.getJWTData(tenantIdentifierWithStorage, sessionHandle); + JsonElement jwtPayload = Session.getJWTData(tenantIdentifier, storage, sessionHandle); JsonObject result = new JsonObject(); @@ -129,7 +131,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } catch (StorageQueryException e) { throw new ServletException(e); } catch (UnauthorisedException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "UNAUTHORISED"); reply.addProperty("message", e.getMessage()); diff --git a/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java index e875ce909..81e8e306f 100644 --- a/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java @@ -26,9 +26,11 @@ import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.session.Session; @@ -72,34 +74,33 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I assert enableAntiCsrf != null; assert refreshToken != null; - AppIdentifierWithStorage appIdentifierWithStorage = null; + TenantIdentifier tenantIdentifierForLogging = null; try { - appIdentifierWithStorage = this.getAppIdentifierWithStorage(req); + tenantIdentifierForLogging = getTenantIdentifier(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { + AppIdentifier appIdentifier = this.getAppIdentifier(req); AccessToken.VERSION accessTokenVersion = AccessToken.getAccessTokenVersionForCDI(version); - SessionInformationHolder sessionInfo = Session.refreshSession(appIdentifierWithStorage, main, + SessionInformationHolder sessionInfo = Session.refreshSession(appIdentifier, main, refreshToken, antiCsrfToken, enableAntiCsrf, accessTokenVersion, - useDynamicSigningKey == null ? null : Boolean.FALSE.equals(useDynamicSigningKey) - ); + useDynamicSigningKey == null ? null : Boolean.FALSE.equals(useDynamicSigningKey)); + TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), + appIdentifier.getAppId(), sessionInfo.session.tenantId); + Storage storage = StorageLayer.getStorage(tenantIdentifier, main); - if (StorageLayer.getStorage(this.getTenantIdentifierWithStorageFromRequest(req), main).getType() == - STORAGE_TYPE.SQL) { + if (storage.getType() == STORAGE_TYPE.SQL) { try { UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - this.getAppIdentifierWithStorage(req), - sessionInfo.session.userId, UserIdType.ANY); + appIdentifier, storage, sessionInfo.session.userId, UserIdType.ANY); if (userIdMapping != null) { - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, - userIdMapping.superTokensUserId); + ActiveUsers.updateLastActive(appIdentifier, main, userIdMapping.superTokensUserId); } else { - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, - sessionInfo.session.userId); + ActiveUsers.updateLastActive(appIdentifier, main, sessionInfo.session.userId); } } catch (StorageQueryException ignored) { } @@ -123,14 +124,14 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I UnsupportedJWTSigningAlgorithmException e) { throw new ServletException(e); } catch (AccessTokenPayloadError | UnauthorisedException e) { - Logging.debug(main, appIdentifierWithStorage.getAsPublicTenantIdentifier(), + Logging.debug(main, tenantIdentifierForLogging, Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "UNAUTHORISED"); reply.addProperty("message", e.getMessage()); super.sendJsonResponse(200, reply, resp); } catch (TokenTheftDetectedException e) { - Logging.debug(main, appIdentifierWithStorage.getAsPublicTenantIdentifier(), + Logging.debug(main, tenantIdentifierForLogging, Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "TOKEN_THEFT_DETECTED"); diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java index 7691ee81e..7af0fa841 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java @@ -27,11 +27,11 @@ import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.session.SessionInfo; import io.supertokens.session.Session; @@ -85,7 +85,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I assert userDataInDatabase != null; try { - boolean useStaticSigningKey = !Config.getConfig(this.getTenantIdentifierWithStorageFromRequest(req), main) + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); + + boolean useStaticSigningKey = !Config.getConfig(tenantIdentifier, main) .getAccessTokenSigningKeyDynamic(); if (version.greaterThanOrEqualTo(SemVer.v2_21)) { Boolean useDynamicSigningKey = InputParser.parseBooleanOrThrowError(input, "useDynamicSigningKey", @@ -98,22 +101,21 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I AccessToken.VERSION accessTokenVersion = AccessToken.getAccessTokenVersionForCDI(version); SessionInformationHolder sessionInfo = Session.createNewSession( - this.getTenantIdentifierWithStorageFromRequest(req), main, userId, userDataInJWT, + tenantIdentifier, storage, main, userId, userDataInJWT, userDataInDatabase, enableAntiCsrf, accessTokenVersion, useStaticSigningKey); - if (StorageLayer.getStorage(this.getTenantIdentifierWithStorageFromRequest(req), main).getType() == - STORAGE_TYPE.SQL) { + if (storage.getType() == STORAGE_TYPE.SQL) { try { io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - this.getAppIdentifierWithStorage(req), + tenantIdentifier.toAppIdentifier(), storage, sessionInfo.session.userId, UserIdType.ANY); if (userIdMapping != null) { - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, + ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, userIdMapping.superTokensUserId); } else { - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, + ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, sessionInfo.session.userId); } } catch (StorageQueryException ignored) { @@ -134,7 +136,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I if (super.getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v2_21)) { result.remove("idRefreshToken"); } else { - Utils.addLegacySigningKeyInfos(this.getAppIdentifierWithStorage(req), main, result, + Utils.addLegacySigningKeyInfos(tenantIdentifier.toAppIdentifier(), main, result, super.getVersionFromRequest(req).betweenInclusive(SemVer.v2_9, SemVer.v2_21)); } @@ -155,18 +157,19 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO String sessionHandle = InputParser.getQueryParamOrThrowError(req, "sessionHandle", false); assert sessionHandle != null; - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - AppIdentifierWithStorage appIdentifier = getAppIdentifierWithStorage(req); - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), + AppIdentifier appIdentifier = getAppIdentifier(req); + tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); - tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + storage = StorageLayer.getStorage(tenantIdentifier, main); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { - SessionInfo sessionInfo = Session.getSession(tenantIdentifierWithStorage, sessionHandle); + SessionInfo sessionInfo = Session.getSession(tenantIdentifier, storage, sessionHandle); JsonObject result = new Gson().toJsonTree(sessionInfo).getAsJsonObject(); result.add("userDataInJWT", Utils.toJsonTreeWithNulls(sessionInfo.userDataInJWT)); @@ -175,7 +178,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO result.addProperty("status", "OK"); if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v3_0)) { - result.addProperty("tenantId", tenantIdentifierWithStorage.getTenantId()); + result.addProperty("tenantId", tenantIdentifier.getTenantId()); } if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { result.remove("recipeUserId"); @@ -186,7 +189,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } catch (StorageQueryException e) { throw new ServletException(e); } catch (UnauthorisedException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "UNAUTHORISED"); reply.addProperty("message", e.getMessage()); diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionDataAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionDataAPI.java index 3243f5781..12d512227 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionDataAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionDataAPI.java @@ -22,10 +22,10 @@ import io.supertokens.exceptions.UnauthorisedException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.session.Session; import io.supertokens.session.accessToken.AccessToken; @@ -59,17 +59,18 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO String sessionHandle = InputParser.getQueryParamOrThrowError(req, "sessionHandle", false); assert sessionHandle != null; - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - AppIdentifierWithStorage appIdentifier = getAppIdentifierWithStorage(req); - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); - tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + AppIdentifier appIdentifier = getAppIdentifier(req); + tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); + storage = StorageLayer.getStorage(tenantIdentifier, main); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { - JsonObject userDataInDatabase = Session.getSessionData(tenantIdentifierWithStorage, sessionHandle); + JsonObject userDataInDatabase = Session.getSessionData(tenantIdentifier, storage, sessionHandle); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -79,7 +80,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } catch (StorageQueryException e) { throw new ServletException(e); } catch (UnauthorisedException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "UNAUTHORISED"); reply.addProperty("message", e.getMessage()); @@ -96,11 +97,12 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject userDataInDatabase = InputParser.parseJsonObjectOrThrowError(input, "userDataInDatabase", false); assert userDataInDatabase != null; - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + TenantIdentifier tenantIdentifier; + Storage storage; try { - AppIdentifierWithStorage appIdentifier = getAppIdentifierWithStorage(req); - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); - tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + AppIdentifier appIdentifier = getAppIdentifier(req); + tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); + storage = StorageLayer.getStorage(tenantIdentifier, main); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } @@ -110,10 +112,10 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO // which is always null here if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v2_21)) { AccessToken.VERSION version = AccessToken.getAccessTokenVersionForCDI(getVersionFromRequest(req)); - Session.updateSession(tenantIdentifierWithStorage, sessionHandle, + Session.updateSession(tenantIdentifier, storage, sessionHandle, userDataInDatabase, null, version); } else { - Session.updateSessionBeforeCDI2_21(tenantIdentifierWithStorage, sessionHandle, + Session.updateSessionBeforeCDI2_21(tenantIdentifier, storage, sessionHandle, userDataInDatabase, null); } @@ -126,7 +128,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } catch (AccessTokenPayloadError e) { throw new ServletException(new BadRequestException(e.getMessage())); } catch (UnauthorisedException e) { - Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "UNAUTHORISED"); reply.addProperty("message", e.getMessage()); diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionRegenerateAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionRegenerateAPI.java index 0f2843e65..e177dfe90 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionRegenerateAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionRegenerateAPI.java @@ -26,7 +26,7 @@ import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.session.Session; import io.supertokens.session.info.SessionInformationHolder; @@ -68,17 +68,17 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject userDataInJWT = InputParser.parseJsonObjectOrThrowError(input, "userDataInJWT", true); - AppIdentifierWithStorage appIdentifierWithStorage = null; + AppIdentifier appIdentifier = null; try { - appIdentifierWithStorage = this.getAppIdentifierWithStorage(req); + appIdentifier = this.getAppIdentifier(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } try { SessionInformationHolder sessionInfo = getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v2_21) ? - Session.regenerateToken(this.getAppIdentifierWithStorage(req), main, accessToken, userDataInJWT) : - Session.regenerateTokenBeforeCDI2_21(appIdentifierWithStorage, main, accessToken, + Session.regenerateToken(appIdentifier, main, accessToken, userDataInJWT) : + Session.regenerateTokenBeforeCDI2_21(appIdentifier, main, accessToken, userDataInJWT); JsonObject result = sessionInfo.toJsonObject(); @@ -98,7 +98,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I UnsupportedJWTSigningAlgorithmException | TenantOrAppNotFoundException e) { throw new ServletException(e); } catch (UnauthorisedException | TryRefreshTokenException e) { - Logging.debug(main, appIdentifierWithStorage.getAsPublicTenantIdentifier(), + Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "UNAUTHORISED"); 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 22fba74cf..2686de03d 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java @@ -21,9 +21,15 @@ import com.google.gson.JsonPrimitive; import io.supertokens.ActiveUsers; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.session.Session; @@ -36,6 +42,10 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class SessionRemoveAPI extends WebserverAPI { private static final long serialVersionUID = -2082970815993229316L; @@ -51,7 +61,7 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - // API is tenant specific. also operates on all tenants in an app if `revokeAcrossAllTenants` is set to true + // API is tenant specific, but ignores tenantId from the request if revoking from all tenants JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String userId = InputParser.parseStringOrThrowError(input, "userId", true); @@ -98,28 +108,45 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } if (userId != null) { + Storage storage = null; try { 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); + storage = storageAndUserIdMapping.storage; + } catch (UnknownUserIdException e) { + storage = getTenantStorage(req); + } sessionHandlesRevoked = Session.revokeAllSessionsForUser( - main, this.getAppIdentifierWithStorage(req), userId, revokeSessionsForLinkedAccounts); + main, appIdentifier, storage, userId, revokeSessionsForLinkedAccounts); } else { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + storage = getTenantStorage(req); + sessionHandlesRevoked = Session.revokeAllSessionsForUser( - main, this.getTenantIdentifierWithStorageFromRequest(req), userId, - revokeSessionsForLinkedAccounts); + main, tenantIdentifier, storage, userId, revokeSessionsForLinkedAccounts); } - if (StorageLayer.getStorage(this.getTenantIdentifierWithStorageFromRequest(req), main).getType() == - STORAGE_TYPE.SQL) { + if (storage.getType() == STORAGE_TYPE.SQL) { try { + AppIdentifier appIdentifier = getAppIdentifier(req); UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - this.getAppIdentifierWithStorage(req), - userId, UserIdType.ANY); + appIdentifier, storage, userId, UserIdType.ANY); if (userIdMapping != null) { - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, + ActiveUsers.updateLastActive(appIdentifier, main, userIdMapping.superTokensUserId); } else { - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, userId); + ActiveUsers.updateLastActive(appIdentifier, main, userId); } } catch (StorageQueryException ignored) { } @@ -137,12 +164,34 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } } else { try { - String[] sessionHandlesRevoked = Session.revokeSessionUsingSessionHandles(main, - this.getAppIdentifierWithStorage(req), sessionHandles); + AppIdentifier appIdentifier = getAppIdentifier(req); + Map> sessionHandlesByTenantId = new HashMap<>(); + + for (String sessionHandle : sessionHandles) { + String tenantId = Session.getTenantIdFromSessionHandle(sessionHandle); + if (!sessionHandlesByTenantId.containsKey(tenantId)) { + sessionHandlesByTenantId.put(tenantId, new ArrayList<>()); + } + sessionHandlesByTenantId.get(tenantId).add(sessionHandle); + } + List allSessionHandlesRevoked = new ArrayList<>(); + for (Map.Entry> entry : sessionHandlesByTenantId.entrySet()) { + String tenantId = entry.getKey(); + List sessionHandlesForTenant = entry.getValue(); + Storage storage = StorageLayer.getStorage( + new TenantIdentifier( + appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), tenantId), + main); + + String[] sessionHandlesRevoked = Session.revokeSessionUsingSessionHandles(main, + appIdentifier, storage, sessionHandlesForTenant.toArray(new String[0])); + allSessionHandlesRevoked.addAll(List.of(sessionHandlesRevoked)); + } + JsonObject result = new JsonObject(); result.addProperty("status", "OK"); JsonArray sessionHandlesRevokedJSON = new JsonArray(); - for (String sessionHandle : sessionHandlesRevoked) { + for (String sessionHandle : allSessionHandlesRevoked) { sessionHandlesRevokedJSON.add(new JsonPrimitive(sessionHandle)); } result.add("sessionHandlesRevoked", sessionHandlesRevokedJSON); 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 e31c32ee1..8e7b53e2e 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java @@ -20,10 +20,18 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.session.Session; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.useridmapping.UserIdType; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -47,7 +55,7 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - // API is tenant specific, also finds across all tenants in the app if fetchAcrossAllTenants is set to true + // API is tenant specific, but ignores tenantId if fetchAcrossAllTenants is true String userId = InputParser.getQueryParamOrThrowError(req, "userId", false); assert userId != null; @@ -67,12 +75,32 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO try { 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); + storage = storageAndUserIdMapping.storage; + } catch (UnknownUserIdException e) { + storage = getTenantStorage(req); + } sessionHandles = Session.getAllNonExpiredSessionHandlesForUser( - main, this.getAppIdentifierWithStorage(req), userId, fetchSessionsForAllLinkedAccounts); + main, appIdentifier, storage, userId, + fetchSessionsForAllLinkedAccounts); } else { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); sessionHandles = Session.getAllNonExpiredSessionHandlesForUser( - this.getTenantIdentifierWithStorageFromRequest(req), userId, fetchSessionsForAllLinkedAccounts); + tenantIdentifier, storage, userId, fetchSessionsForAllLinkedAccounts); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/session/VerifySessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/VerifySessionAPI.java index 8b3fd7d84..99e0f2d17 100644 --- a/src/main/java/io/supertokens/webserver/api/session/VerifySessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/VerifySessionAPI.java @@ -67,11 +67,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I Boolean enableAntiCsrf = InputParser.parseBooleanOrThrowError(input, "enableAntiCsrf", false); assert enableAntiCsrf != null; - AppIdentifier appIdentifier; + AppIdentifier appIdentifier = null; try { - // We actually don't use the storage because tenantId is obtained from the accessToken, - // and appropriate storage is obtained later - appIdentifier = this.getAppIdentifierWithStorage(req); + appIdentifier = this.getAppIdentifier(req); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } @@ -135,7 +133,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I reply.addProperty("jwtSigningPublicKeyExpiryTime", SigningKeys.getInstance(appIdentifier, main).getDynamicSigningKeyExpiryTime()); - Utils.addLegacySigningKeyInfos(this.getAppIdentifierWithStorage(req), main, reply, + Utils.addLegacySigningKeyInfos(appIdentifier, main, reply, super.getVersionFromRequest(req).betweenInclusive(SemVer.v2_9, SemVer.v2_21)); } diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java index 391db2c70..66fcc8758 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java @@ -21,14 +21,13 @@ import com.google.gson.JsonObject; import io.supertokens.Main; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; -import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; @@ -56,14 +55,13 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // this API is tenant specific try { - TenantIdentifierWithStorage tenantIdentifierWithStorage = this.getTenantIdentifierWithStorageFromRequest( - req); - AppIdentifierWithStorage appIdentifierWithStorage = this.getAppIdentifierWithStorage(req); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); String email = InputParser.getQueryParamOrThrowError(req, "email", false); email = Utils.normaliseEmail(email); - AuthRecipeUserInfo[] users = ThirdParty.getUsersByEmail(tenantIdentifierWithStorage, email); - UserIdMapping.populateExternalUserIdForUsers(tenantIdentifierWithStorage, users); + AuthRecipeUserInfo[] users = ThirdParty.getUsersByEmail(tenantIdentifier, storage, email); + UserIdMapping.populateExternalUserIdForUsers(storage, users); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index 5f50557ad..acd7812f5 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -23,9 +23,11 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; @@ -75,13 +77,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I email = Utils.normaliseEmail(email); try { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); ThirdParty.SignInUpResponse response = ThirdParty.signInUp2_7( - this.getTenantIdentifierWithStorageFromRequest(req), super.main, - thirdPartyId, - thirdPartyUserId, email, isEmailVerified); - UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{response.user}); + tenantIdentifier, storage, + thirdPartyId, thirdPartyUserId, email, isEmailVerified); + UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{response.user}); - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, response.user.getSupertokensUserId()); + ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, + response.user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -135,12 +139,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I email = Utils.normaliseEmail(email); try { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); ThirdParty.SignInUpResponse response = ThirdParty.signInUp( - this.getTenantIdentifierWithStorageFromRequest(req), super.main, thirdPartyId, thirdPartyUserId, + tenantIdentifier, storage, super.main, thirdPartyId, thirdPartyUserId, email, isEmailVerified); - UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{response.user}); + UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{response.user}); - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, response.user.getSupertokensUserId()); + ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, + response.user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java index 2ce566281..f1089b011 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java @@ -17,12 +17,16 @@ package io.supertokens.webserver.api.thirdparty; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; @@ -74,27 +78,32 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO AuthRecipeUserInfo user = null; if (userId != null) { // Query by userId + AppIdentifier appIdentifier = getAppIdentifier(req); try { - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(req, userId, + UserIdType.ANY, true); // if a userIdMapping exists, pass the superTokensUserId to the getUserUsingId function - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - userId = appIdentifierWithStorageAndUserIdMapping.userIdMapping.superTokensUserId; + if (storageAndUserIdMapping.userIdMapping != null) { + userId = storageAndUserIdMapping.userIdMapping.superTokensUserId; } - user = ThirdParty.getUser(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, + user = ThirdParty.getUser(appIdentifier, storageAndUserIdMapping.storage, userId); if (user != null) { - UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); + UserIdMapping.populateExternalUserIdForUsers(storageAndUserIdMapping.storage, + new AuthRecipeUserInfo[]{user}); } } catch (UnknownUserIdException e) { // let the user be null } } else { - user = ThirdParty.getUser(this.getTenantIdentifierWithStorageFromRequest(req), thirdPartyId, + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); + user = ThirdParty.getUser(tenantIdentifier, storage, thirdPartyId, thirdPartyUserId); if (user != null) { - UserIdMapping.populateExternalUserIdForUsers(getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{user}); + UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{user}); } } @@ -118,7 +127,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO super.sendJsonResponse(200, result, resp); } - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } diff --git a/src/main/java/io/supertokens/webserver/api/totp/CreateOrUpdateTotpDeviceAPI.java b/src/main/java/io/supertokens/webserver/api/totp/CreateOrUpdateTotpDeviceAPI.java index 01bc2c28a..1243bec42 100644 --- a/src/main/java/io/supertokens/webserver/api/totp/CreateOrUpdateTotpDeviceAPI.java +++ b/src/main/java/io/supertokens/webserver/api/totp/CreateOrUpdateTotpDeviceAPI.java @@ -1,14 +1,15 @@ package io.supertokens.webserver.api.totp; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.totp.TOTPDevice; import io.supertokens.pluginInterface.totp.exception.DeviceAlreadyExistsException; @@ -66,26 +67,21 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); try { - AppIdentifierWithStorage appIdentifierWithStorage; + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage; try { // This step is required only because user_last_active table stores supertokens internal user id. // While sending the usage stats we do a join, so totp tables also must use internal user id. // Try to find the appIdentifier with right storage based on the userId - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, userId, UserIdType.ANY); - - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; - } - appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + StorageAndUserIdMapping storageAndUserIdMapping = enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + storage = storageAndUserIdMapping.storage; } catch (UnknownUserIdException e) { - // if the user is not found, just use the storage of the tenant of interest - appIdentifierWithStorage = getAppIdentifierWithStorage(req); + throw new IllegalStateException("should never happen"); } - TOTPDevice device = Totp.registerDevice(appIdentifierWithStorage, main, userId, deviceName, skew, period); + TOTPDevice device = Totp.registerDevice(appIdentifier, storage, main, userId, deviceName, skew, period); result.addProperty("status", "OK"); result.addProperty("deviceName", device.deviceName); @@ -95,7 +91,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I result.addProperty("status", "DEVICE_ALREADY_EXISTS_ERROR"); super.sendJsonResponse(200, result, resp); } catch (StorageQueryException | NoSuchAlgorithmException | FeatureNotEnabledException | - TenantOrAppNotFoundException | StorageTransactionLogicException e) { + TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } @@ -122,26 +118,23 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject result = new JsonObject(); try { - AppIdentifierWithStorage appIdentifierWithStorage; + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage; try { // This step is required only because user_last_active table stores supertokens internal user id. // While sending the usage stats we do a join, so totp tables also must use internal user id. // Try to find the appIdentifier with right storage based on the userId - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, userId, UserIdType.ANY); - - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; - } - appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + StorageAndUserIdMapping storageAndUserIdMapping = + enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + storage = storageAndUserIdMapping.storage; + } catch (UnknownUserIdException e) { - // if the user is not found, just use the storage of the tenant of interest - appIdentifierWithStorage = getAppIdentifierWithStorage(req); + throw new IllegalStateException("should never happen"); } - Totp.updateDeviceName(appIdentifierWithStorage, userId, existingDeviceName, newDeviceName); + Totp.updateDeviceName(appIdentifier, storage, userId, existingDeviceName, newDeviceName); result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); @@ -151,7 +144,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } catch (DeviceAlreadyExistsException e) { result.addProperty("status", "DEVICE_ALREADY_EXISTS_ERROR"); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/totp/GetTotpDevicesAPI.java b/src/main/java/io/supertokens/webserver/api/totp/GetTotpDevicesAPI.java index 98da43d5c..0f7c86d59 100644 --- a/src/main/java/io/supertokens/webserver/api/totp/GetTotpDevicesAPI.java +++ b/src/main/java/io/supertokens/webserver/api/totp/GetTotpDevicesAPI.java @@ -2,12 +2,14 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.totp.TOTPDevice; import io.supertokens.totp.Totp; @@ -44,26 +46,21 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject result = new JsonObject(); try { - AppIdentifierWithStorage appIdentifierWithStorage; + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage; try { // This step is required only because user_last_active table stores supertokens internal user id. // While sending the usage stats we do a join, so totp tables also must use internal user id. // Try to find the appIdentifier with right storage based on the userId - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, userId, UserIdType.ANY); - - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; - } - appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + StorageAndUserIdMapping storageAndUserIdMapping = enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + storage = storageAndUserIdMapping.storage; } catch (UnknownUserIdException e) { - // if the user is not found, just use the storage of the tenant of interest - appIdentifierWithStorage = getAppIdentifierWithStorage(req); + throw new IllegalStateException("should never happen"); } - TOTPDevice[] devices = Totp.getDevices(appIdentifierWithStorage, - userId); + TOTPDevice[] devices = Totp.getDevices(appIdentifier, storage, userId); JsonArray devicesArray = new JsonArray(); for (TOTPDevice d : devices) { @@ -79,7 +76,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO result.addProperty("status", "OK"); result.add("devices", devicesArray); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/totp/ImportTotpDeviceAPI.java b/src/main/java/io/supertokens/webserver/api/totp/ImportTotpDeviceAPI.java index 3fc6b3621..22ea7118b 100644 --- a/src/main/java/io/supertokens/webserver/api/totp/ImportTotpDeviceAPI.java +++ b/src/main/java/io/supertokens/webserver/api/totp/ImportTotpDeviceAPI.java @@ -1,18 +1,18 @@ package io.supertokens.webserver.api.totp; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.totp.TOTPDevice; import io.supertokens.pluginInterface.totp.exception.DeviceAlreadyExistsException; -import io.supertokens.pluginInterface.totp.exception.UnknownDeviceException; import io.supertokens.totp.Totp; import io.supertokens.useridmapping.UserIdType; import io.supertokens.webserver.InputParser; @@ -22,7 +22,6 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import java.security.NoSuchAlgorithmException; public class ImportTotpDeviceAPI extends WebserverAPI { private static final long serialVersionUID = -4641988458637882374L; @@ -70,26 +69,21 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); try { - AppIdentifierWithStorage appIdentifierWithStorage; - try { - // This step is required only because user_last_active table stores supertokens internal user id. - // While sending the usage stats we do a join, so totp tables also must use internal user id. - - // Try to find the appIdentifier with right storage based on the userId - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, userId, UserIdType.ANY); + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage; + // This step is required only because user_last_active table stores supertokens internal user id. + // While sending the usage stats we do a join, so totp tables also must use internal user id. - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; - } - appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + // Try to find the appIdentifier with right storage based on the userId + try { + StorageAndUserIdMapping mappingAndStorage = enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + storage = mappingAndStorage.storage; } catch (UnknownUserIdException e) { - // if the user is not found, just use the storage of the tenant of interest - appIdentifierWithStorage = getAppIdentifierWithStorage(req); + throw new IllegalStateException("should never happen"); } - TOTPDevice createdDevice = Totp.createDevice(super.main, appIdentifierWithStorage, + TOTPDevice createdDevice = Totp.createDevice(super.main, appIdentifier, storage, userId, deviceName, skew, period, secretKey, true, System.currentTimeMillis()); result.addProperty("status", "OK"); @@ -99,7 +93,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I result.addProperty("status", "DEVICE_ALREADY_EXISTS_ERROR"); super.sendJsonResponse(200, result, resp); } catch (StorageQueryException | FeatureNotEnabledException | - TenantOrAppNotFoundException e) { + TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/totp/RemoveTotpDeviceAPI.java b/src/main/java/io/supertokens/webserver/api/totp/RemoveTotpDeviceAPI.java index d6b0c6f50..0501d5dfe 100644 --- a/src/main/java/io/supertokens/webserver/api/totp/RemoveTotpDeviceAPI.java +++ b/src/main/java/io/supertokens/webserver/api/totp/RemoveTotpDeviceAPI.java @@ -1,13 +1,15 @@ package io.supertokens.webserver.api.totp; import com.google.gson.JsonObject; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.totp.exception.UnknownDeviceException; import io.supertokens.totp.Totp; @@ -50,26 +52,21 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); try { - AppIdentifierWithStorage appIdentifierWithStorage; + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage; try { // This step is required only because user_last_active table stores supertokens internal user id. // While sending the usage stats we do a join, so totp tables also must use internal user id. // Try to find the appIdentifier with right storage based on the userId - AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = - getAppIdentifierWithStorageAndUserIdMappingFromRequest( - req, userId, UserIdType.ANY); - - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; - } - appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + StorageAndUserIdMapping storageAndUserIdMapping = enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + storage = storageAndUserIdMapping.storage; } catch (UnknownUserIdException e) { - // if the user is not found, just use the storage of the tenant of interest - appIdentifierWithStorage = getAppIdentifierWithStorage(req); + throw new IllegalStateException("should never happen"); } - Totp.removeDevice(appIdentifierWithStorage, userId, deviceName); + Totp.removeDevice(appIdentifier, storage, userId, deviceName); result.addProperty("status", "OK"); result.addProperty("didDeviceExist", true); @@ -78,7 +75,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I result.addProperty("status", "OK"); result.addProperty("didDeviceExist", false); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | + BadPermissionException e) { throw new ServletException(e); } } 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 07a1df325..83b5f16b4 100644 --- a/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpAPI.java @@ -5,20 +5,18 @@ import com.google.gson.JsonObject; import io.supertokens.Main; -import io.supertokens.TenantIdentifierWithStorageAndUserIdMapping; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.totp.exception.UnknownTotpUserIdException; import io.supertokens.totp.Totp; import io.supertokens.totp.exceptions.InvalidTotpException; import io.supertokens.totp.exceptions.LimitReachedException; -import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.SemVer; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -55,24 +53,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); try { - TenantIdentifierWithStorage tenantIdentifierWithStorage; - try { - // This step is required only because user_last_active table stores supertokens internal user id. - // While sending the usage stats we do a join, so totp tables also must use internal user id. + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); - TenantIdentifierWithStorageAndUserIdMapping mappingAndStorage = getTenantIdentifierWithStorageAndUserIdMappingFromRequest( - req, userId, UserIdType.ANY); - - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; - } - tenantIdentifierWithStorage = mappingAndStorage.tenantIdentifierWithStorage; - } catch (UnknownUserIdException e) { - // if the user is not found, just use the storage of the tenant of interest - tenantIdentifierWithStorage = getTenantIdentifierWithStorageFromRequest(req); - } - - Totp.verifyCode(tenantIdentifierWithStorage, main, userId, totp); + Totp.verifyCode(tenantIdentifier, storage, main, userId, totp); result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); 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 0cdfe0a8c..770ee7bd9 100644 --- a/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpDeviceAPI.java +++ b/src/main/java/io/supertokens/webserver/api/totp/VerifyTotpDeviceAPI.java @@ -5,12 +5,11 @@ import com.google.gson.JsonObject; import io.supertokens.Main; -import io.supertokens.TenantIdentifierWithStorageAndUserIdMapping; import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.totp.exception.UnknownDeviceException; import io.supertokens.totp.Totp; @@ -58,23 +57,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); try { - TenantIdentifierWithStorage tenantIdentifierWithStorage; - try { - // This step is required only because user_last_active table stores supertokens internal user id. - // While sending the usage stats we do a join, so totp tables also must use internal user id. - - TenantIdentifierWithStorageAndUserIdMapping mappingAndStorage = getTenantIdentifierWithStorageAndUserIdMappingFromRequest( - req, userId, UserIdType.ANY); - - if (mappingAndStorage.userIdMapping != null) { - userId = mappingAndStorage.userIdMapping.superTokensUserId; - } - tenantIdentifierWithStorage = mappingAndStorage.tenantIdentifierWithStorage; - } catch (UnknownUserIdException e) { - // if the user is not found, just use the storage of the tenant of interest - tenantIdentifierWithStorage = getTenantIdentifierWithStorageFromRequest(req); - } - boolean isNewlyVerified = Totp.verifyDevice(tenantIdentifierWithStorage, main, userId, deviceName, totp); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req);; + boolean isNewlyVerified = Totp.verifyDevice(tenantIdentifier, storage, main, userId, deviceName, totp); result.addProperty("status", "OK"); result.addProperty("wasAlreadyVerified", !isNewlyVerified); diff --git a/src/main/java/io/supertokens/webserver/api/useridmapping/RemoveUserIdMappingAPI.java b/src/main/java/io/supertokens/webserver/api/useridmapping/RemoveUserIdMappingAPI.java index 26d71902f..e37e87ef7 100644 --- a/src/main/java/io/supertokens/webserver/api/useridmapping/RemoveUserIdMappingAPI.java +++ b/src/main/java/io/supertokens/webserver/api/useridmapping/RemoveUserIdMappingAPI.java @@ -18,11 +18,12 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; import io.supertokens.webserver.InputParser; @@ -84,17 +85,19 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, userIdType); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(req, userId, userIdType, + true); boolean didMappingExist = UserIdMapping.deleteUserIdMapping( - appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId, userIdType, force); + getAppIdentifier(req), + storageAndUserIdMapping.storage, userId, userIdType, force); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("didMappingExist", didMappingExist); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { diff --git a/src/main/java/io/supertokens/webserver/api/useridmapping/UpdateExternalUserIdInfoAPI.java b/src/main/java/io/supertokens/webserver/api/useridmapping/UpdateExternalUserIdInfoAPI.java index e1cae3fae..80c3f31b4 100644 --- a/src/main/java/io/supertokens/webserver/api/useridmapping/UpdateExternalUserIdInfoAPI.java +++ b/src/main/java/io/supertokens/webserver/api/useridmapping/UpdateExternalUserIdInfoAPI.java @@ -18,11 +18,12 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; import io.supertokens.webserver.InputParser; @@ -93,11 +94,13 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, userIdType); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(req, userId, userIdType, + true); if (UserIdMapping.updateOrDeleteExternalUserIdInfo( - appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId, userIdType, externalUserIdInfo)) { + getAppIdentifier(req), + storageAndUserIdMapping.storage, userId, userIdType, externalUserIdInfo)) { JsonObject response = new JsonObject(); response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); @@ -108,7 +111,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO response.addProperty("status", "UNKNOWN_MAPPING_ERROR"); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { diff --git a/src/main/java/io/supertokens/webserver/api/useridmapping/UserIdMappingAPI.java b/src/main/java/io/supertokens/webserver/api/useridmapping/UserIdMappingAPI.java index 2afc24650..475e30f93 100644 --- a/src/main/java/io/supertokens/webserver/api/useridmapping/UserIdMappingAPI.java +++ b/src/main/java/io/supertokens/webserver/api/useridmapping/UserIdMappingAPI.java @@ -18,13 +18,14 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.useridmapping.exception.UnknownSuperTokensUserIdException; import io.supertokens.pluginInterface.useridmapping.exception.UserIdMappingAlreadyExistsException; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.SemVer; @@ -94,10 +95,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, superTokensUserId, UserIdType.SUPERTOKENS); - - UserIdMapping.createUserIdMapping(main, appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, + UserIdMapping.createUserIdMapping( + getAppIdentifier(req), enforcePublicTenantAndGetAllStoragesForApp(req), superTokensUserId, externalUserId, externalUserIdInfo, force, getVersionFromRequest(req).greaterThanOrEqualTo( SemVer.v4_0)); @@ -105,7 +104,7 @@ superTokensUserId, externalUserId, externalUserIdInfo, force, getVersionFromRequ response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); - } catch (UnknownSuperTokensUserIdException | UnknownUserIdException e) { + } catch (UnknownSuperTokensUserIdException e) { JsonObject response = new JsonObject(); response.addProperty("status", "UNKNOWN_SUPERTOKENS_USER_ID_ERROR"); super.sendJsonResponse(200, response, resp); @@ -117,7 +116,7 @@ superTokensUserId, externalUserId, externalUserIdInfo, force, getVersionFromRequ response.addProperty("doesExternalUserIdExist", e.doesExternalUserIdExist); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } @@ -163,10 +162,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // Request from (app1, tenant1) will return user1 and request from (app1, tenant2) will return user2 // Request from (app1, tenant3) may result in either user1 or user2 - AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = - this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, userIdType); + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(req, userId, userIdType, true); - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping == null) { + if (storageAndUserIdMapping.userIdMapping == null) { JsonObject response = new JsonObject(); response.addProperty("status", "UNKNOWN_MAPPING_ERROR"); super.sendJsonResponse(200, response, resp); @@ -176,16 +175,16 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("superTokensUserId", - appIdentifierWithStorageAndUserIdMapping.userIdMapping.superTokensUserId); + storageAndUserIdMapping.userIdMapping.superTokensUserId); response.addProperty("externalUserId", - appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserIdInfo != null) { + storageAndUserIdMapping.userIdMapping.externalUserId); + if (storageAndUserIdMapping.userIdMapping.externalUserIdInfo != null) { response.addProperty("externalUserIdInfo", - appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserIdInfo); + storageAndUserIdMapping.userIdMapping.externalUserIdInfo); } super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } catch (UnknownUserIdException e) { diff --git a/src/main/java/io/supertokens/webserver/api/usermetadata/RemoveUserMetadataAPI.java b/src/main/java/io/supertokens/webserver/api/usermetadata/RemoveUserMetadataAPI.java index 174d0da91..a4ca1c77b 100644 --- a/src/main/java/io/supertokens/webserver/api/usermetadata/RemoveUserMetadataAPI.java +++ b/src/main/java/io/supertokens/webserver/api/usermetadata/RemoveUserMetadataAPI.java @@ -18,9 +18,14 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.useridmapping.UserIdType; import io.supertokens.usermetadata.UserMetadata; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -47,12 +52,23 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I // API is app specific JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String userId = InputParser.parseStringOrThrowError(input, "userId", false); + try { - UserMetadata.deleteUserMetadata(this.getAppIdentifierWithStorage(req), userId); + AppIdentifier appIdentifier = getAppIdentifier(req); + + try { + StorageAndUserIdMapping storageAndUserIdMapping = + this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + UserMetadata.deleteUserMetadata(appIdentifier, storageAndUserIdMapping.storage, userId); + } catch (UnknownUserIdException e) { + throw new IllegalStateException("should never happen"); + } + JsonObject response = new JsonObject(); response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/usermetadata/UserMetadataAPI.java b/src/main/java/io/supertokens/webserver/api/usermetadata/UserMetadataAPI.java index 9586c3a37..6326fb160 100644 --- a/src/main/java/io/supertokens/webserver/api/usermetadata/UserMetadataAPI.java +++ b/src/main/java/io/supertokens/webserver/api/usermetadata/UserMetadataAPI.java @@ -18,10 +18,15 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.StorageAndUserIdMapping; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.useridmapping.UserIdType; import io.supertokens.usermetadata.UserMetadata; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -47,13 +52,23 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific String userId = InputParser.getQueryParamOrThrowError(req, "userId", false); + try { - JsonObject metadata = UserMetadata.getUserMetadata(this.getAppIdentifierWithStorage(req), userId); + AppIdentifier appIdentifier = getAppIdentifier(req); + JsonObject metadata; + try { + StorageAndUserIdMapping storageAndUserIdMapping = this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + metadata = UserMetadata.getUserMetadata(appIdentifier, storageAndUserIdMapping.storage, userId); + } catch (UnknownUserIdException e) { + throw new IllegalStateException("should never happen"); + } + JsonObject response = new JsonObject(); response.add("metadata", metadata); response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } @@ -64,13 +79,25 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String userId = InputParser.parseStringOrThrowError(input, "userId", false); JsonObject update = InputParser.parseJsonObjectOrThrowError(input, "metadataUpdate", false); + try { - JsonObject metadata = UserMetadata.updateUserMetadata(this.getAppIdentifierWithStorage(req), userId, update); + AppIdentifier appIdentifier = getAppIdentifier(req); + JsonObject metadata; + + try { + StorageAndUserIdMapping storageAndUserIdMapping = this.enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi( + req, userId, UserIdType.ANY, false); + metadata = UserMetadata.updateUserMetadata(appIdentifier, storageAndUserIdMapping.storage, userId, + update); + } catch (UnknownUserIdException e) { + throw new IllegalStateException("should never happen"); + } + JsonObject response = new JsonObject(); response.add("metadata", metadata); response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/userroles/AddUserRoleAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/AddUserRoleAPI.java index 92e88357c..cbfe89873 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/AddUserRoleAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/AddUserRoleAPI.java @@ -18,6 +18,8 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -60,8 +62,11 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } try { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); + boolean didUserAlreadyHaveRole = !UserRoles.addRoleToUser( - this.getTenantIdentifierWithStorageFromRequest(req), userId, role); + main, tenantIdentifier, storage, userId, role); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("didUserAlreadyHaveRole", didUserAlreadyHaveRole); diff --git a/src/main/java/io/supertokens/webserver/api/userroles/CreateRoleAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/CreateRoleAPI.java index c24d88e14..24b74473a 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/CreateRoleAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/CreateRoleAPI.java @@ -19,6 +19,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -80,15 +83,18 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } try { + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req); boolean createdNewRole = UserRoles.createNewRoleOrModifyItsPermissions( - this.getAppIdentifierWithStorage(req), role, permissions); + appIdentifier, storage, role, permissions); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("createdNewRole", createdNewRole); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | + BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/userroles/GetPermissionsForRoleAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/GetPermissionsForRoleAPI.java index 38973448e..7b9fab585 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/GetPermissionsForRoleAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/GetPermissionsForRoleAPI.java @@ -20,6 +20,9 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.Main; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -59,7 +62,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - String[] permissions = UserRoles.getPermissionsForRole(this.getAppIdentifierWithStorage(req), role); + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req); + + String[] permissions = UserRoles.getPermissionsForRole(appIdentifier, storage, role); JsonArray arr = new JsonArray(); for (String permission : permissions) { arr.add(new JsonPrimitive(permission)); @@ -72,7 +78,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject response = new JsonObject(); response.addProperty("status", "UNKNOWN_ROLE_ERROR"); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/userroles/GetRolesAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/GetRolesAPI.java index 1519a746e..21ca5d73b 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/GetRolesAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/GetRolesAPI.java @@ -20,6 +20,9 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.Main; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -49,8 +52,10 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific try { + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req); - String[] roles = UserRoles.getRoles(this.getAppIdentifierWithStorage(req)); + String[] roles = UserRoles.getRoles(appIdentifier, storage); JsonArray arr = new JsonArray(); for (String s : roles) { @@ -62,7 +67,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/userroles/GetRolesForPermissionAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/GetRolesForPermissionAPI.java index e2e295f3b..6577c93b5 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/GetRolesForPermissionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/GetRolesForPermissionAPI.java @@ -20,6 +20,9 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.Main; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -59,8 +62,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req); - String[] roles = UserRoles.getRolesThatHavePermission(this.getAppIdentifierWithStorage(req), permission); + String[] roles = UserRoles.getRolesThatHavePermission(appIdentifier, storage, permission); JsonArray arr = new JsonArray(); for (String s : roles) { @@ -72,7 +77,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/userroles/GetRolesForUserAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/GetRolesForUserAPI.java index d4efe7c93..ebc500f21 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/GetRolesForUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/GetRolesForUserAPI.java @@ -20,6 +20,8 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.Main; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -51,9 +53,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // API is tenant specific String userId = InputParser.getQueryParamOrThrowError(req, "userId", false); try { - - String[] userRoles = UserRoles.getRolesForUser(this.getTenantIdentifierWithStorageFromRequest(req), - userId); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); + String[] userRoles = UserRoles.getRolesForUser(tenantIdentifier, storage, userId); JsonArray arr = new JsonArray(); for (String s : userRoles) { arr.add(new JsonPrimitive(s)); diff --git a/src/main/java/io/supertokens/webserver/api/userroles/GetUsersForRoleAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/GetUsersForRoleAPI.java index 47e0901bc..87f3d3f08 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/GetUsersForRoleAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/GetUsersForRoleAPI.java @@ -20,6 +20,8 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.Main; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -60,8 +62,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); - String[] roleUsers = UserRoles.getUsersForRole(this.getTenantIdentifierWithStorageFromRequest(req), role); + String[] roleUsers = UserRoles.getUsersForRole(tenantIdentifier, storage, role); JsonArray arr = new JsonArray(); for (String s : roleUsers) { diff --git a/src/main/java/io/supertokens/webserver/api/userroles/RemovePermissionsForRoleAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/RemovePermissionsForRoleAPI.java index ca0b19f3f..7300e9b6b 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/RemovePermissionsForRoleAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/RemovePermissionsForRoleAPI.java @@ -19,6 +19,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -80,11 +83,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - UserRoles.deletePermissionsFromRole(this.getAppIdentifierWithStorage(req), role, permissions); + AppIdentifier appIdentifier = getAppIdentifier(req); + Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req); + + UserRoles.deletePermissionsFromRole(appIdentifier, storage, role, permissions); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | + BadPermissionException e) { throw new ServletException(e); } catch (UnknownRoleException e) { JsonObject response = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/userroles/RemoveRoleAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/RemoveRoleAPI.java index fe5914bda..71898b33c 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/RemoveRoleAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/RemoveRoleAPI.java @@ -18,6 +18,8 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -57,13 +59,16 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - boolean didRoleExist = UserRoles.deleteRole(this.getAppIdentifierWithStorage(req), role); + AppIdentifier appIdentifier = getAppIdentifier(req); + enforcePublicTenantAndGetPublicTenantStorage(req); // enforce this API is called from public tenant + + boolean didRoleExist = UserRoles.deleteRole(main, appIdentifier, role); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("didRoleExist", didRoleExist); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/userroles/RemoveUserRoleAPI.java b/src/main/java/io/supertokens/webserver/api/userroles/RemoveUserRoleAPI.java index 38a13d0ad..7b76eb31b 100644 --- a/src/main/java/io/supertokens/webserver/api/userroles/RemoveUserRoleAPI.java +++ b/src/main/java/io/supertokens/webserver/api/userroles/RemoveUserRoleAPI.java @@ -18,6 +18,8 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -60,8 +62,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - boolean didUserHaveRole = UserRoles.removeUserRole(this.getTenantIdentifierWithStorageFromRequest(req), - userId, role); + TenantIdentifier tenantIdentifier = getTenantIdentifier(req); + Storage storage = getTenantStorage(req); + + boolean didUserHaveRole = UserRoles.removeUserRole(tenantIdentifier, storage, userId, role); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); diff --git a/src/test/java/io/supertokens/test/FeatureFlagTest.java b/src/test/java/io/supertokens/test/FeatureFlagTest.java index 408351098..47abe83aa 100644 --- a/src/test/java/io/supertokens/test/FeatureFlagTest.java +++ b/src/test/java/io/supertokens/test/FeatureFlagTest.java @@ -31,7 +31,7 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -301,7 +301,7 @@ public void testThatCallingGetFeatureFlagAPIReturnsMfaStats() throws Exception { int totalMfaUsers = mfaStats.get("totalUserCountWithMoreThanOneLoginMethodOrTOTPEnabled").getAsInt(); JsonArray mfaMaus = mfaStats.get("mauWithMoreThanOneLoginMethodOrTOTPEnabled").getAsJsonArray(); - assert mfaMaus.size() == 30; + assert mfaMaus.size() == 31; assert mfaMaus.get(0).getAsInt() == 2; // 1 TOTP user + 1 account linked user assert mfaMaus.get(29).getAsInt() == 2; @@ -344,7 +344,7 @@ public void testThatCallingGetFeatureFlagAPIReturnsMfaStats() throws Exception { int totalMfaUsers = mfaStats.get("totalUserCountWithMoreThanOneLoginMethodOrTOTPEnabled").getAsInt(); JsonArray mfaMaus = mfaStats.get("mauWithMoreThanOneLoginMethodOrTOTPEnabled").getAsJsonArray(); - assert mfaMaus.size() == 30; + assert mfaMaus.size() == 31; assert mfaMaus.get(0).getAsInt() == 2; // 1 TOTP user + 1 account linked user assert mfaMaus.get(29).getAsInt() == 2; @@ -363,7 +363,7 @@ public void testThatCallingGetFeatureFlagAPIReturnsMfaStats() throws Exception { ), false); Multitenancy.addUserIdToTenant( process.getProcess(), - new TenantIdentifier(null, null, "t1").withStorage(StorageLayer.getStorage(process.getProcess())), + new TenantIdentifier(null, null, "t1"), (StorageLayer.getStorage(process.getProcess())), signUpResponse.get("user").getAsJsonObject().get("id").getAsString() ); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -380,7 +380,7 @@ public void testThatCallingGetFeatureFlagAPIReturnsMfaStats() throws Exception { int totalMfaUsers = mfaStats.get("totalUserCountWithMoreThanOneLoginMethodOrTOTPEnabled").getAsInt(); JsonArray mfaMaus = mfaStats.get("mauWithMoreThanOneLoginMethodOrTOTPEnabled").getAsJsonArray(); - assert mfaMaus.size() == 30; + assert mfaMaus.size() == 31; assert mfaMaus.get(0).getAsInt() == 2; // 1 TOTP user + 1 account linked user assert mfaMaus.get(29).getAsInt() == 2; @@ -489,15 +489,17 @@ public void testThatMultitenantStatsAreAccurate() throws Exception { ) ); - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage( + Storage storage = ( StorageLayer.getStorage(tenantIdentifier, process.getProcess())); if (i % 3 == 0) { // Create a user EmailPassword.signUp( - tenantIdentifierWithStorage, process.getProcess(), "user@example.com", "password"); + tenantIdentifier, storage, process.getProcess(), "user@example.com", + "password"); } else if (i % 3 == 1) { // Create a session - Session.createNewSession(tenantIdentifierWithStorage, process.getProcess(), "userid", new JsonObject(), + Session.createNewSession( + tenantIdentifier, storage, process.getProcess(), "userid", new JsonObject(), new JsonObject()); } else { // Create an enterprise provider @@ -609,15 +611,17 @@ public void testThatMultitenantStatsAreAccurateForAnApp() throws Exception { ) ); - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage( + Storage storage = ( StorageLayer.getStorage(tenantIdentifier, process.getProcess())); if (i % 3 == 0) { // Create a user EmailPassword.signUp( - tenantIdentifierWithStorage, process.getProcess(), "user@example.com", "password"); + tenantIdentifier, storage, process.getProcess(), "user@example.com", + "password"); } else if (i % 3 == 1) { // Create a session - Session.createNewSession(tenantIdentifierWithStorage, process.getProcess(), "userid", new JsonObject(), + Session.createNewSession( + tenantIdentifier, storage, process.getProcess(), "userid", new JsonObject(), new JsonObject()); } else { // Create an enterprise provider @@ -739,15 +743,17 @@ public void testThatMultitenantStatsAreAccurateForACud() throws Exception { ) ); - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage( + Storage storage = ( StorageLayer.getStorage(tenantIdentifier, process.getProcess())); if (i % 3 == 0) { // Create a user EmailPassword.signUp( - tenantIdentifierWithStorage, process.getProcess(), "user@example.com", "password"); + tenantIdentifier, storage, process.getProcess(), "user@example.com", + "password"); } else if (i % 3 == 1) { // Create a session - Session.createNewSession(tenantIdentifierWithStorage, process.getProcess(), "userid", new JsonObject(), + Session.createNewSession( + tenantIdentifier, storage, process.getProcess(), "userid", new JsonObject(), new JsonObject()); } else { // Create an enterprise provider diff --git a/src/test/java/io/supertokens/test/PathRouterTest.java b/src/test/java/io/supertokens/test/PathRouterTest.java index b58d80664..3f5879adb 100644 --- a/src/test/java/io/supertokens/test/PathRouterTest.java +++ b/src/test/java/io/supertokens/test/PathRouterTest.java @@ -20,7 +20,6 @@ import com.google.gson.JsonPrimitive; import io.supertokens.ProcessState; import io.supertokens.ProcessState.PROCESS_STATE; -import io.supertokens.config.Config; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; @@ -51,7 +50,6 @@ import org.mockito.Mockito; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import static org.junit.Assert.*; @@ -138,9 +136,13 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); } @@ -309,9 +311,13 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); } @@ -481,9 +487,13 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); } @@ -664,9 +674,13 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); } @@ -847,9 +861,13 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); } @@ -1012,11 +1030,15 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId() + - ",", - resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId() + + ",", + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }, new WebserverAPI(process.getProcess(), "r1") { @@ -1035,11 +1057,15 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId() + - ",r1", - resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId() + + ",r1", + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } })); @@ -1060,9 +1086,13 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); @@ -1130,11 +1160,15 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId() + - ",", - resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId() + + ",", + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } })); @@ -1156,9 +1190,13 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); fail(); @@ -1195,10 +1233,14 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId() + ",", - resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId() + ",", + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }, new WebserverAPI(process.getProcess(), "r1") { @@ -1217,10 +1259,14 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId() + ",r1", - resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId() + ",r1", + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } })); fail(); @@ -1247,10 +1293,14 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId() + ",", - resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId() + ",", + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }, new WebserverAPI(process.getProcess(), "r1") { @@ -1269,10 +1319,14 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId() + ",r1", - resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId() + ",r1", + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } })); fail(); @@ -1360,8 +1414,12 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, super.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, super.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); @@ -1473,9 +1531,13 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, super.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), - resp); + try { + super.sendTextResponse(200, super.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); @@ -1589,9 +1651,13 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, super.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), - resp); + try { + super.sendTextResponse(200, super.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getTenantId(), + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); @@ -1728,10 +1794,14 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getAppId() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getAppId() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); } @@ -2029,10 +2099,14 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getAppId() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getAppId() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); } @@ -2314,10 +2388,14 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - this.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getAppId() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + this.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getAppId() + "," + + this.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); } @@ -2561,10 +2639,14 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, super.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getAppId() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), - resp); + try { + super.sendTextResponse(200, super.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getAppId() + "," + + this.getTenantIdentifier(req).getTenantId(), + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); @@ -2710,10 +2792,14 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, super.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getAppId() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), - resp); + try { + super.sendTextResponse(200, super.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getAppId() + "," + + this.getTenantIdentifier(req).getTenantId(), + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); @@ -2857,10 +2943,14 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, super.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - this.getTenantIdentifierFromRequest(req).getAppId() + "," + - this.getTenantIdentifierFromRequest(req).getTenantId(), - resp); + try { + super.sendTextResponse(200, super.getTenantIdentifier(req).getConnectionUriDomain() + "," + + this.getTenantIdentifier(req).getAppId() + "," + + this.getTenantIdentifier(req).getTenantId(), + resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); diff --git a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java index 9e3c23661..4417f756f 100644 --- a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java @@ -29,6 +29,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.multitenancy.*; @@ -137,7 +138,7 @@ public void testThatCreationOfPrimaryUserRequiresAccountLinkingFeatureToBeEnable try { AuthRecipe.createPrimaryUser(process.main, - new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(process.main)), ""); + new AppIdentifier(null, null), StorageLayer.getStorage(process.main), ""); assert (false); } catch (FeatureNotEnabledException e) { assert (e.getMessage() @@ -424,9 +425,9 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), null, null, new JsonObject())); - TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", - StorageLayer.getStorage(process.main)); - AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + Storage storage = (StorageLayer.getStorage(process.main)); + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(new TenantIdentifier(null, null, "t1"), + storage, process.getProcess(), "test@example.com", "pass1234"); @@ -436,7 +437,9 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", "test@example.com"); - Multitenancy.addUserIdToTenant(process.main, tenantIdentifierWithStorage, signInUpResponse.user.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.main, new TenantIdentifier(null, null, "t1"), + storage, + signInUpResponse.user.getSupertokensUserId()); try { AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); @@ -470,9 +473,9 @@ public void makePrimarySucceedsEvenIfAnotherAccountWithSameEmailButInADifferentT new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), null, null, new JsonObject())); - TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", - StorageLayer.getStorage(process.main)); - AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + Storage storage = (StorageLayer.getStorage(process.main)); + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(new TenantIdentifier(null, null, "t1"), + storage, process.getProcess(), "test@example.com", "pass1234"); diff --git a/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java b/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java index ca1cb8d1d..e920629b2 100644 --- a/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java @@ -27,12 +27,12 @@ import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -114,9 +114,10 @@ public void testListUsersByAccountInfoForUnlinkedAccounts() throws Exception { AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); + Storage storage = (StorageLayer.getBaseStorage(process.getProcess())); - AuthRecipeUserInfo userToTest = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + AuthRecipeUserInfo userToTest = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", null, null, null)[0]; assertNotNull(userToTest.getSupertokensUserId()); assertFalse(userToTest.isPrimaryUser); @@ -128,19 +129,30 @@ public void testListUsersByAccountInfoForUnlinkedAccounts() throws Exception { assert(userToTest.loginMethods[0].timeJoined > 0); // test for result - assertEquals(user1, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null)[0]); - assertEquals(user2, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, null, "google", "userid1")[0]); - assertEquals(user2, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test2@example.com", null, "google", "userid1")[0]); - assertEquals(user3, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test3@example.com", null, null, null)[0]); - assertEquals(user4, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, "+919876543210", null, null)[0]); + assertEquals(user1, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage + , false, "test1@example.com", null, null, null)[0]); + assertEquals(user2, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage + , false, null, null, "google", "userid1")[0]); + assertEquals(user2, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage + , false, "test2@example.com", null, "google", "userid1")[0]); + assertEquals(user3, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage + , false, "test3@example.com", null, null, null)[0]); + assertEquals(user4, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage + , false, null, "+919876543210", null, null)[0]); // test for no result - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", "+919876543210", null, null).length); - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test2@example.com", "+919876543210", null, null).length); - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test3@example.com", "+919876543210", null, null).length); - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, "+919876543210", "google", "userid1").length); - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@gmail.com", null, "google", "userid1").length); - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test3@gmail.com", null, "google", "userid1").length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, "test1@example.com", "+919876543210", null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, "test2@example.com", "+919876543210", null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, "test3@example.com", "+919876543210", null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, null, "+919876543210", "google", "userid1").length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, "test1@gmail.com", null, "google", "userid1").length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, "test3@gmail.com", null, "google", "userid1").length); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -165,27 +177,31 @@ public void testListUsersByAccountInfoForUnlinkedAccountsWithUnionOption() throw AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); + Storage storage = (StorageLayer.getBaseStorage(process.getProcess())); { - AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, true, "test1@example.com", "+919876543210", null, null); + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, true, "test1@example.com", "+919876543210", null, null); assertEquals(2, users.length); assertTrue(Arrays.asList(users).contains(user1)); assertTrue(Arrays.asList(users).contains(user4)); } { - AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, true, "test1@example.com", null, "google", "userid1"); + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, true, "test1@example.com", null, "google", "userid1"); assertEquals(2, users.length); assertTrue(Arrays.asList(users).contains(user1)); assertTrue(Arrays.asList(users).contains(user2)); } { - AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, true, null, "+919876543210", "google", "userid1"); + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, true, null, "+919876543210", "google", "userid1"); assertEquals(2, users.length); assertTrue(Arrays.asList(users).contains(user4)); assertTrue(Arrays.asList(users).contains(user2)); } { - AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, true, "test1@example.com", "+919876543210", "google", "userid1"); + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, true, "test1@example.com", "+919876543210", "google", "userid1"); assertEquals(3, users.length); assertTrue(Arrays.asList(users).contains(user1)); assertTrue(Arrays.asList(users).contains(user2)); @@ -210,11 +226,15 @@ public void testUnknownAccountInfo() throws Exception { return; } - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null).length); - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, null, "google", "userid1").length); - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test3@example.com", null, null, null).length); - assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, "+919876543210", null, null).length); + Storage storage = (StorageLayer.getBaseStorage(process.getProcess())); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, "test1@example.com", null, null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, null, null, "google", "userid1").length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, "test3@example.com", null, null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, storage, + false, null, "+919876543210", null, null).length); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -241,20 +261,25 @@ public void testListUserByAccountInfoWhenAccountsAreLinked1() throws Exception { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + Storage storage = ( StorageLayer.getBaseStorage(process.getProcess())); primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", null, null, null)[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test2@example.com", null, null, null)[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, null, null, "google", "userid1")[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", null, "google", "userid1")[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test2@example.com", null, "google", "userid1")[0]); process.kill(); @@ -282,14 +307,16 @@ public void testListUserByAccountInfoWhenAccountsAreLinked2() throws Exception { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + Storage storage = ( StorageLayer.getBaseStorage(process.getProcess())); primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", null, null, null)[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test2@example.com", null, null, null)[0]); process.kill(); @@ -317,14 +344,16 @@ public void testListUserByAccountInfoWhenAccountsAreLinked3() throws Exception { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + Storage storage = ( StorageLayer.getBaseStorage(process.getProcess())); primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", null, null, null)[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test2@example.com", null, null, null)[0]); process.kill(); @@ -352,16 +381,19 @@ public void testListUserByAccountInfoWhenAccountsAreLinked4() throws Exception { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + Storage storage = ( StorageLayer.getBaseStorage(process.getProcess())); primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", null, null, null)[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, null, "+919876543210", null, null)[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", "+919876543210", null, null)[0]); process.kill(); @@ -389,20 +421,25 @@ public void testListUserByAccountInfoWhenAccountsAreLinked5() throws Exception { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + Storage storage = ( StorageLayer.getBaseStorage(process.getProcess())); primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", null, null, null)[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test2@example.com", null, null, null)[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, null, null, "google", "userid1")[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", null, "google", "userid1")[0]); - assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test2@example.com", null, "google", "userid1")[0]); process.kill(); @@ -436,20 +473,23 @@ public void testForEmptyResults() throws Exception { AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + Storage storage = ( StorageLayer.getBaseStorage(process.getProcess())); { - AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test5@example.com", null, null, null); assertEquals(0, users.length); } { - AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, null, null, "google", "userid5"); assertEquals(0, users.length); } { - AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, null, "+9876", null, null); assertEquals(0, users.length); } @@ -491,11 +531,12 @@ public void testGetUserByAccountInfoOrdersUserBasedOnTimeJoined() throws Excepti AuthRecipe.createPrimaryUser(process.getProcess(), user4.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + Storage storage = ( StorageLayer.getBaseStorage(process.getProcess())); { - AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, true, "test1@example.com", null, + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, true, "test1@example.com", null, null, null); assertEquals(3, users.length); @@ -504,7 +545,8 @@ public void testGetUserByAccountInfoOrdersUserBasedOnTimeJoined() throws Excepti } { - AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(TenantIdentifier.BASE_TENANT, + storage, false, "test1@example.com", null, null, null); assertEquals(3, users.length); diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java index 8f4be10fe..4f32473d1 100644 --- a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -29,6 +29,7 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.multitenancy.*; @@ -467,17 +468,19 @@ public void linkAccountFailureCauseAccountInfoAssociatedWithAPrimaryUserEvenIfIn new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), null, null, new JsonObject())); - TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", - StorageLayer.getStorage(process.main)); + Storage storage = (StorageLayer.getStorage(process.main)); - AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + AuthRecipeUserInfo user = + EmailPassword.signUp(new TenantIdentifier(null, null, "t1"), storage, + process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); Thread.sleep(50); - ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(tenantIdentifierWithStorage, + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp( + new TenantIdentifier(null, null, "t1"), storage, process.getProcess(), "google", "user-google", "test@example.com"); @@ -520,10 +523,10 @@ public void linkAccountSuccessAcrossTenants() throws Exception { new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), null, null, new JsonObject())); - TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", - StorageLayer.getStorage(process.main)); + Storage storage = (StorageLayer.getStorage(process.main)); - AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + AuthRecipeUserInfo user = EmailPassword.signUp(new TenantIdentifier(null, null, "t1"), + storage, process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java index c4e463db4..54241e317 100644 --- a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java @@ -33,6 +33,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.PhoneNumberChangeNotAllowedException; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; @@ -184,23 +185,27 @@ public void testUserAreNotAutomaticallySharedBetweenTenantsOfLinkedAccountsForPl t1 = new TenantIdentifier(null, "a1", null); t2 = new TenantIdentifier(null, "a1", "t1"); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test@example.com", "password"); - Passwordless.CreateCodeResponse user2Code = Passwordless.createCode(t1WithStorage, process.getProcess(), + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", + "password"); + Passwordless.CreateCodeResponse user2Code = Passwordless.createCode(t1, t1Storage, process.getProcess(), "test@example.com", null, null, null); - AuthRecipeUserInfo user2 = Passwordless.consumeCode(t1WithStorage, process.getProcess(), user2Code.deviceId, user2Code.deviceIdHash, user2Code.userInputCode, null).user; + AuthRecipeUserInfo user2 = Passwordless.consumeCode(t1, t1Storage, process.getProcess(), user2Code.deviceId, + user2Code.deviceIdHash, user2Code.userInputCode, null).user; - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), t1.toAppIdentifier(), t1Storage, + user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1.toAppIdentifier(), t1Storage, user2.getSupertokensUserId(), + user1.getSupertokensUserId()); - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), t2, t2Storage, user1.getSupertokensUserId()); { // user2 should not be shared in tenant2 - Passwordless.CreateCodeResponse user3Code = Passwordless.createCode(t2WithStorage, process.getProcess(), + Passwordless.CreateCodeResponse user3Code = Passwordless.createCode(t2, t2Storage, process.getProcess(), "test@example.com", null, null, null); - Passwordless.ConsumeCodeResponse res = Passwordless.consumeCode(t2WithStorage, process.getProcess(), + Passwordless.ConsumeCodeResponse res = Passwordless.consumeCode(t2, t2Storage, process.getProcess(), user3Code.deviceId, user3Code.deviceIdHash, user3Code.userInputCode, null); assertTrue(res.createdNewUser); } @@ -228,19 +233,23 @@ public void testUserAreNotAutomaticallySharedBetweenTenantsOfLinkedAccountsForTP t1 = new TenantIdentifier(null, "a1", null); t2 = new TenantIdentifier(null, "a1", "t1"); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test@example.com", "password"); - AuthRecipeUserInfo user2 = ThirdParty.signInUp(t1WithStorage, process.getProcess(), "google", "googleid1", "test@example.com").user; + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", + "password"); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(t1, t1Storage, process.getProcess(), "google", "googleid1", + "test@example.com").user; - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), t1.toAppIdentifier(), t1Storage, + user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1.toAppIdentifier(), t1Storage, user2.getSupertokensUserId(), + user1.getSupertokensUserId()); - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), t2, t2Storage, user1.getSupertokensUserId()); { // user2 should not be shared in tenant2 - ThirdParty.SignInUpResponse res = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", + ThirdParty.SignInUpResponse res = ThirdParty.signInUp(t2, t2Storage, process.getProcess(), "google", "googleid1", "test@example.com"); assertTrue(res.createdNewUser); } @@ -268,23 +277,29 @@ public void testTenantDeletionWithAccountLinking() throws Exception { t1 = new TenantIdentifier(null, "a1", null); t2 = new TenantIdentifier(null, "a1", "t1"); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test@example.com", "password"); - AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", "googleid1", "test@example.com").user; + AuthRecipeUserInfo user1 = EmailPassword.signUp(t2, t2Storage, process.getProcess(), "test@example.com", + "password"); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2, t2Storage, process.getProcess(), "google", "googleid1", + "test@example.com").user; - AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), t2.toAppIdentifier(), t2Storage, + user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t2.toAppIdentifier(), t2Storage, user2.getSupertokensUserId(), + user1.getSupertokensUserId()); Multitenancy.deleteTenant(t2, process.getProcess()); - AuthRecipeUserInfo getUser1 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipeUserInfo getUser1 = AuthRecipe.getUserById(t1.toAppIdentifier(), t1Storage, + user1.getSupertokensUserId()); for (LoginMethod lm : getUser1.loginMethods) { assertEquals(0, lm.tenantIds.size()); } - AuthRecipeUserInfo getUser2 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId()); + AuthRecipeUserInfo getUser2 = AuthRecipe.getUserById(t1.toAppIdentifier(), t1Storage, + user2.getSupertokensUserId()); for (LoginMethod lm : getUser2.loginMethods) { assertEquals(0, lm.tenantIds.size()); } @@ -312,36 +327,44 @@ public void testTenantDeletionWithAccountLinkingWithUserRoles() throws Exception t1 = new TenantIdentifier(null, "a1", null); t2 = new TenantIdentifier(null, "a1", "t1"); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test@example.com", "password"); - AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", "googleid1", "test@example.com").user; + AuthRecipeUserInfo user1 = EmailPassword.signUp(t2, t2Storage, process.getProcess(), "test@example.com", + "password"); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2, t2Storage, process.getProcess(), "google", "googleid1", + "test@example.com").user; - AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), t2.toAppIdentifier(), t2Storage, + user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t2.toAppIdentifier(), t2Storage, user2.getSupertokensUserId(), + user1.getSupertokensUserId()); - UserRoles.createNewRoleOrModifyItsPermissions(t2WithStorage.toAppIdentifierWithStorage(), "admin", new String[]{"p1"}); - UserRoles.addRoleToUser(t2WithStorage, user1.getSupertokensUserId(), "admin"); + UserRoles.createNewRoleOrModifyItsPermissions(t2.toAppIdentifier(), t2Storage, "admin", new String[]{"p1"}); + UserRoles.addRoleToUser(process.getProcess(), t2, t2Storage, user1.getSupertokensUserId(), "admin"); Multitenancy.deleteTenant(t2, process.getProcess()); createTenants(process.getProcess()); // create the tenant again - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); // add the user to the tenant again - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user2.getSupertokensUserId()); // add the user to the tenant again + Multitenancy.addUserIdToTenant(process.getProcess(), t2, t2Storage, user1.getSupertokensUserId()); // add + // the user to the tenant again + Multitenancy.addUserIdToTenant(process.getProcess(), t2, t2Storage, user2.getSupertokensUserId()); // add + // the user to the tenant again - AuthRecipeUserInfo getUser1 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipeUserInfo getUser1 = AuthRecipe.getUserById(t1.toAppIdentifier(), t1Storage, + user1.getSupertokensUserId()); for (LoginMethod lm : getUser1.loginMethods) { assertEquals(1, lm.tenantIds.size()); } - AuthRecipeUserInfo getUser2 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId()); + AuthRecipeUserInfo getUser2 = AuthRecipe.getUserById(t1.toAppIdentifier(), t1Storage, + user2.getSupertokensUserId()); for (LoginMethod lm : getUser2.loginMethods) { assertEquals(1, lm.tenantIds.size()); } - String[] roles = UserRoles.getRolesForUser(t2WithStorage, user1.getSupertokensUserId()); + String[] roles = UserRoles.getRolesForUser(t2, t2Storage, user1.getSupertokensUserId()); assertEquals(0, roles.length); // must be deleted with tenant process.kill(); @@ -691,8 +714,9 @@ public void testVariousCases() throws Exception { new TestCaseStep() { @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, main)); - AuthRecipeUserInfo user = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), TestCase.users.get(0).getSupertokensUserId()); + Storage t1Storage = (StorageLayer.getStorage(t1, main)); + AuthRecipeUserInfo user = AuthRecipe.getUserById(t1.toAppIdentifier(), t1Storage, + TestCase.users.get(0).getSupertokensUserId()); assertEquals(2, user.loginMethods.length); assertTrue(user.loginMethods[0].tenantIds.contains(t2.getTenantId())); assertTrue(user.loginMethods[1].tenantIds.contains(t1.getTenantId())); @@ -732,15 +756,17 @@ public void execute(Main main) throws Exception { new TestCaseStep() { @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, main)); - AuthRecipe.deleteUser(t1WithStorage.toAppIdentifierWithStorage(), TestCase.users.get(1).getSupertokensUserId()); + Storage t1Storage = (StorageLayer.getStorage(t1, main)); + AuthRecipe.deleteUser(t1.toAppIdentifier(), t1Storage, + TestCase.users.get(1).getSupertokensUserId()); } }, new TestCaseStep() { @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, main)); - AuthRecipeUserInfo user = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), TestCase.users.get(0).getSupertokensUserId()); + Storage t1Storage = (StorageLayer.getStorage(t1, main)); + AuthRecipeUserInfo user = AuthRecipe.getUserById(t1.toAppIdentifier(), t1Storage, + TestCase.users.get(0).getSupertokensUserId()); assertNull(user); } } @@ -902,8 +928,9 @@ public CreateEmailPasswordUser(TenantIdentifier tenantIdentifier, String email) @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifierWithStorage, main, email, "password"); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifier, storage, main, email, + "password"); TestCase.addUser(user); } } @@ -919,10 +946,13 @@ public CreatePlessUserWithEmail(TenantIdentifier tenantIdentifier, String email) @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - Passwordless.CreateCodeResponse code = Passwordless.createCode(tenantIdentifierWithStorage, main, + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + Passwordless.CreateCodeResponse code = Passwordless.createCode(tenantIdentifier, + storage, main, email, null, null, null); - AuthRecipeUserInfo user = Passwordless.consumeCode(tenantIdentifierWithStorage, main, code.deviceId, code.deviceIdHash, code.userInputCode, null).user; + AuthRecipeUserInfo user = Passwordless.consumeCode(tenantIdentifier, storage, main, + code.deviceId, + code.deviceIdHash, code.userInputCode, null).user; TestCase.addUser(user); } } @@ -938,10 +968,13 @@ public CreatePlessUserWithPhone(TenantIdentifier tenantIdentifier, String phoneN @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - Passwordless.CreateCodeResponse code = Passwordless.createCode(tenantIdentifierWithStorage, main, + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + Passwordless.CreateCodeResponse code = Passwordless.createCode(tenantIdentifier, + storage, main, null, phoneNumber, null, null); - AuthRecipeUserInfo user = Passwordless.consumeCode(tenantIdentifierWithStorage, main, code.deviceId, code.deviceIdHash, code.userInputCode, null).user; + AuthRecipeUserInfo user = Passwordless.consumeCode(tenantIdentifier, storage, main, + code.deviceId, + code.deviceIdHash, code.userInputCode, null).user; TestCase.addUser(user); } } @@ -961,8 +994,10 @@ public CreateThirdPartyUser(TenantIdentifier tenantIdentifier, String thirdParty @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - AuthRecipeUserInfo user = ThirdParty.signInUp(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, email).user; + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipeUserInfo user = ThirdParty.signInUp(tenantIdentifier, storage, main, + thirdPartyId, + thirdPartyUserId, email).user; TestCase.addUser(user); } } @@ -978,8 +1013,9 @@ public MakePrimaryUser(TenantIdentifier tenantIdentifier, int userIndex) { @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - AuthRecipe.createPrimaryUser(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId()); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipe.createPrimaryUser(main, tenantIdentifier.toAppIdentifier(), storage, + TestCase.users.get(userIndex).getSupertokensUserId()); } } @@ -996,8 +1032,9 @@ public LinkAccounts(TenantIdentifier tenantIdentifier, int primaryUserIndex, int @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - AuthRecipe.linkAccounts(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(recipeUserIndex).getSupertokensUserId(), TestCase.users.get(primaryUserIndex).getSupertokensUserId()); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipe.linkAccounts(main, tenantIdentifier.toAppIdentifier(), storage, + TestCase.users.get(recipeUserIndex).getSupertokensUserId(), TestCase.users.get(primaryUserIndex).getSupertokensUserId()); } } @@ -1012,8 +1049,9 @@ public AssociateUserToTenant(TenantIdentifier tenantIdentifier, int userIndex) { @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - Multitenancy.addUserIdToTenant(main, tenantIdentifierWithStorage, TestCase.users.get(userIndex).getSupertokensUserId()); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + Multitenancy.addUserIdToTenant(main, tenantIdentifier, storage, + TestCase.users.get(userIndex).getSupertokensUserId()); } } @@ -1030,8 +1068,9 @@ public UpdateEmailPasswordUserEmail(TenantIdentifier tenantIdentifier, int userI @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - EmailPassword.updateUsersEmailOrPassword(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main, TestCase.users.get(userIndex).getSupertokensUserId(), email, null); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + EmailPassword.updateUsersEmailOrPassword(tenantIdentifier.toAppIdentifier(), storage, + main, TestCase.users.get(userIndex).getSupertokensUserId(), email, null); } } @@ -1048,8 +1087,9 @@ public UpdatePlessUserEmail(TenantIdentifier tenantIdentifier, int userIndex, St @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - Passwordless.updateUser(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId(), new Passwordless.FieldUpdate(email), null); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + Passwordless.updateUser(tenantIdentifier.toAppIdentifier(), storage, + TestCase.users.get(userIndex).getSupertokensUserId(), new Passwordless.FieldUpdate(email), null); } } @@ -1066,8 +1106,9 @@ public UpdatePlessUserPhone(TenantIdentifier tenantIdentifier, int userIndex, St @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - Passwordless.updateUser(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId(), null, new Passwordless.FieldUpdate(phoneNumber)); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + Passwordless.updateUser(tenantIdentifier.toAppIdentifier(), storage, + TestCase.users.get(userIndex).getSupertokensUserId(), null, new Passwordless.FieldUpdate(phoneNumber)); } } @@ -1082,8 +1123,9 @@ public UnlinkAccount(TenantIdentifier tenantIdentifier, int userIndex) { @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - AuthRecipe.unlinkAccounts(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId()); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipe.unlinkAccounts(main, tenantIdentifier.toAppIdentifier(), storage, + TestCase.users.get(userIndex).getSupertokensUserId()); } } @@ -1098,8 +1140,9 @@ public SignInEmailPasswordUser(TenantIdentifier tenantIdentifier, int userIndex) @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - EmailPassword.signIn(tenantIdentifierWithStorage, main, TestCase.users.get(userIndex).loginMethods[0].email, "password"); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + EmailPassword.signIn(tenantIdentifier, storage, main, + TestCase.users.get(userIndex).loginMethods[0].email, "password"); } } @@ -1114,8 +1157,9 @@ public DisassociateUserFromTenant(TenantIdentifier tenantIdentifier, int userInd @Override public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - Multitenancy.removeUserIdFromTenant(main, tenantIdentifierWithStorage, TestCase.users.get(userIndex).getSupertokensUserId(), null); + Storage storage = (StorageLayer.getStorage(tenantIdentifier, main)); + Multitenancy.removeUserIdFromTenant(main, tenantIdentifier, storage, + TestCase.users.get(userIndex).getSupertokensUserId(), null); } } } diff --git a/src/test/java/io/supertokens/test/accountlinking/SessionTests.java b/src/test/java/io/supertokens/test/accountlinking/SessionTests.java index 70a666e70..a747be3cf 100644 --- a/src/test/java/io/supertokens/test/accountlinking/SessionTests.java +++ b/src/test/java/io/supertokens/test/accountlinking/SessionTests.java @@ -29,6 +29,7 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -282,23 +283,27 @@ public void testSessionBehaviourWhenUserBelongsTo2TenantsAndThenLinkedToSomeOthe createTenants(process.getProcess()); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test@example.com", "password"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", + "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test1@example.com", + "password"); - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId()); - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), t1.toAppIdentifier(), + t1Storage, user2.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), t2, t2Storage, user1.getSupertokensUserId()); - SessionInformationHolder session1 = Session.createNewSession(t2WithStorage, process.getProcess(), + SessionInformationHolder session1 = Session.createNewSession(t2, t2Storage, process.getProcess(), user1.getSupertokensUserId(), new JsonObject(), new JsonObject()); // Linking user1 to user2 on t1 should revoke the session - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId(), user2.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1.toAppIdentifier(), t1Storage, + user1.getSupertokensUserId(), user2.getSupertokensUserId()); try { - Session.getSession(t2WithStorage, session1.session.handle); + Session.getSession(t2, t2Storage, session1.session.handle); fail(); } catch (UnauthorisedException e) { // ok @@ -324,23 +329,27 @@ public void testSessionBehaviourWhenUserBelongsTo2TenantsAndThenLinkedToSomeOthe createTenants(process.getProcess()); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test@example.com", "password"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", + "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test1@example.com", + "password"); - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), t1.toAppIdentifier(), + t1Storage, user2.getSupertokensUserId()); - SessionInformationHolder session1 = Session.createNewSession(t2WithStorage, process.getProcess(), + SessionInformationHolder session1 = Session.createNewSession(t2, t2Storage, process.getProcess(), user1.getSupertokensUserId(), new JsonObject(), new JsonObject()); // Linking user1 to user2 on t1 should revoke the session - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId(), user2.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1.toAppIdentifier(), t1Storage, + user1.getSupertokensUserId(), user2.getSupertokensUserId()); try { // session gets removed on t2 as well - Session.getSession(t2WithStorage, session1.session.handle); + Session.getSession(t2, t2Storage, session1.session.handle); fail(); } catch (UnauthorisedException e) { // ok @@ -366,22 +375,26 @@ public void testSessionBehaviourWhenUserBelongsTo2TenantsAndThenLinkedToSomeOthe createTenants(process.getProcess()); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test@example.com", "password"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", + "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test1@example.com", + "password"); - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId(), user2.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), t1.toAppIdentifier(), t1Storage, + user2.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1.toAppIdentifier(), t1Storage, user1.getSupertokensUserId(), + user2.getSupertokensUserId()); - SessionInformationHolder session1 = Session.createNewSession(t2WithStorage, process.getProcess(), + SessionInformationHolder session1 = Session.createNewSession(t2, t2Storage, process.getProcess(), user1.getSupertokensUserId(), new JsonObject(), new JsonObject()); - AuthRecipe.unlinkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId()); + AuthRecipe.unlinkAccounts(process.getProcess(), t1.toAppIdentifier(), t1Storage, user2.getSupertokensUserId()); // session must be intact - Session.getSession(t2WithStorage, session1.session.handle); + Session.getSession(t2, t2Storage, session1.session.handle); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -403,16 +416,20 @@ public void testCreateSessionUsesPrimaryUserIdEvenWhenTheUserIsNotInThatTenant() createTenants(process.getProcess()); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test@example.com", "password"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", + "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test1@example.com", + "password"); - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId(), user2.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), t1.toAppIdentifier(), t1Storage, + user2.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1.toAppIdentifier(), t1Storage, user1.getSupertokensUserId(), + user2.getSupertokensUserId()); - SessionInformationHolder session1 = Session.createNewSession(t2WithStorage, process.getProcess(), + SessionInformationHolder session1 = Session.createNewSession(t2, t2Storage, process.getProcess(), user1.getSupertokensUserId(), new JsonObject(), new JsonObject()); // Should still consider the primaryUserId @@ -448,28 +465,30 @@ public void testGetSessionForUserWithAndWithoutIncludingAllLinkedAccounts() thro new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); + Storage baseTenant = (StorageLayer.getBaseStorage(process.getProcess())); { - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(baseTenant, user1.getSupertokensUserId(), + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(TenantIdentifier.BASE_TENANT, baseTenant, + user1.getSupertokensUserId(), false); assertEquals(1, sessions.length); assertEquals(session1.session.handle, sessions[0]); } { - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(baseTenant, user2.getSupertokensUserId(), + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(TenantIdentifier.BASE_TENANT, baseTenant, + user2.getSupertokensUserId(), false); assertEquals(1, sessions.length); assertEquals(session2.session.handle, sessions[0]); } { - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(baseTenant, user1.getSupertokensUserId(), + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(TenantIdentifier.BASE_TENANT, baseTenant, user1.getSupertokensUserId(), true); assertEquals(2, sessions.length); } { - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(baseTenant, user2.getSupertokensUserId(), + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(TenantIdentifier.BASE_TENANT, baseTenant, user2.getSupertokensUserId(), true); assertEquals(2, sessions.length); } @@ -507,9 +526,9 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( + Storage baseTenant = ( StorageLayer.getBaseStorage(process.getProcess())); - Session.revokeAllSessionsForUser(process.getProcess(), baseTenant, user1.getSupertokensUserId(), true); + Session.revokeAllSessionsForUser(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, user1.getSupertokensUserId(), true); try { Session.getSession(process.getProcess(), session1.session.handle); @@ -533,9 +552,9 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( + Storage baseTenant = ( StorageLayer.getBaseStorage(process.getProcess())); - Session.revokeAllSessionsForUser(process.getProcess(), baseTenant, user2.getSupertokensUserId(), true); + Session.revokeAllSessionsForUser(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, user2.getSupertokensUserId(), true); try { Session.getSession(process.getProcess(), session1.session.handle); @@ -559,9 +578,9 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( + Storage baseTenant = ( StorageLayer.getBaseStorage(process.getProcess())); - Session.revokeAllSessionsForUser(process.getProcess(), baseTenant, user1.getSupertokensUserId(), false); + Session.revokeAllSessionsForUser(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, user1.getSupertokensUserId(), false); try { Session.getSession(process.getProcess(), session1.session.handle); @@ -581,9 +600,9 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( + Storage baseTenant = ( StorageLayer.getBaseStorage(process.getProcess())); - Session.revokeAllSessionsForUser(process.getProcess(), baseTenant, user2.getSupertokensUserId(), false); + Session.revokeAllSessionsForUser(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, user2.getSupertokensUserId(), false); Session.getSession(process.getProcess(), session1.session.handle); diff --git a/src/test/java/io/supertokens/test/accountlinking/TimeJoinedTest.java b/src/test/java/io/supertokens/test/accountlinking/TimeJoinedTest.java index e8bf436eb..d13fbca07 100644 --- a/src/test/java/io/supertokens/test/accountlinking/TimeJoinedTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/TimeJoinedTest.java @@ -24,10 +24,10 @@ import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -120,24 +120,28 @@ public void testThatTimeJoinedIsCorrectWhileAssociatingTenants() throws Exceptio assertEquals(user1.timeJoined, userInfo.timeJoined); } - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getStorage(process.getProcess())); + Storage baseTenant = (StorageLayer.getStorage(process.getProcess())); - Multitenancy.removeUserIdFromTenant(process.getProcess(), baseTenant, user1.getSupertokensUserId(), null); - Multitenancy.removeUserIdFromTenant(process.getProcess(), baseTenant, user2.getSupertokensUserId(), null); + Multitenancy.removeUserIdFromTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, + user1.getSupertokensUserId(), null); + Multitenancy.removeUserIdFromTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, + user2.getSupertokensUserId(), null); { AuthRecipeUserInfo userInfo = AuthRecipe.getUserById(process.getProcess(), user2.getSupertokensUserId()); assertEquals(user1.timeJoined, userInfo.timeJoined); } - Multitenancy.addUserIdToTenant(process.getProcess(), baseTenant, user2.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, + user2.getSupertokensUserId()); { AuthRecipeUserInfo userInfo = AuthRecipe.getUserById(process.getProcess(), user2.getSupertokensUserId()); assertEquals(user1.timeJoined, userInfo.timeJoined); } - Multitenancy.addUserIdToTenant(process.getProcess(), baseTenant, user1.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, + user1.getSupertokensUserId()); { AuthRecipeUserInfo userInfo = AuthRecipe.getUserById(process.getProcess(), user2.getSupertokensUserId()); @@ -169,8 +173,6 @@ public void testUserPaginationIsFineWithUnlinkAndUnlinkAccounts() throws Excepti AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()); AuthRecipe.linkAccounts(process.getProcess(), user1.getSupertokensUserId(), user2.getSupertokensUserId()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getStorage(process.getProcess())); - { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 10, "DESC", null, null, null); @@ -218,7 +220,7 @@ public void testUserPaginationIsFineWithTenantAssociation() throws Exception { AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()); AuthRecipe.linkAccounts(process.getProcess(), user1.getSupertokensUserId(), user2.getSupertokensUserId()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getStorage(process.getProcess())); + Storage baseTenant = (StorageLayer.getStorage(process.getProcess())); { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 10, "DESC", @@ -226,7 +228,8 @@ public void testUserPaginationIsFineWithTenantAssociation() throws Exception { assertEquals(1, users.users.length); } - Multitenancy.removeUserIdFromTenant(process.getProcess(), baseTenant, user1.getSupertokensUserId(), null); + Multitenancy.removeUserIdFromTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, + user1.getSupertokensUserId(), null); { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 10, "DESC", @@ -234,7 +237,8 @@ public void testUserPaginationIsFineWithTenantAssociation() throws Exception { assertEquals(1, users.users.length); } - Multitenancy.addUserIdToTenant(process.getProcess(), baseTenant, user1.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, + user1.getSupertokensUserId()); { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 10, "DESC", @@ -267,8 +271,6 @@ public void testUserSearchWorksWithUnlinkAndLinkAccounts() throws Exception { AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()); AuthRecipe.linkAccounts(process.getProcess(), user1.getSupertokensUserId(), user2.getSupertokensUserId()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getStorage(process.getProcess())); - { ArrayList emails = new ArrayList<>(); emails.add("test"); @@ -322,7 +324,7 @@ public void testUserSearchWorksWithTenantAssociation() throws Exception { AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getStorage(process.getProcess())); + Storage baseTenant = (StorageLayer.getStorage(process.getProcess())); { ArrayList emails = new ArrayList<>(); @@ -332,7 +334,8 @@ public void testUserSearchWorksWithTenantAssociation() throws Exception { assertEquals(1, users.users.length); } - Multitenancy.removeUserIdFromTenant(process.getProcess(), baseTenant, user2.getSupertokensUserId(), null); + Multitenancy.removeUserIdFromTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, + user2.getSupertokensUserId(), null); { ArrayList emails = new ArrayList<>(); @@ -342,7 +345,8 @@ public void testUserSearchWorksWithTenantAssociation() throws Exception { assertEquals(1, users.users.length); } - Multitenancy.addUserIdToTenant(process.getProcess(), baseTenant, user2.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, baseTenant, + user2.getSupertokensUserId()); { ArrayList emails = new ArrayList<>(); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java index 50be6b5d8..c1edbf517 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java @@ -457,7 +457,7 @@ public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { ); AuthRecipeUserInfo user = EmailPassword.signUp( - tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, process.main)), + tenantIdentifier, StorageLayer.getStorage(tenantIdentifier, process.main), process.getProcess(), "test@example.com", "abcd1234"); JsonObject userObj; @@ -497,7 +497,8 @@ public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { } AuthRecipe.createPrimaryUser(process.main, - tenantIdentifier.toAppIdentifier().withStorage(StorageLayer.getStorage(tenantIdentifier, process.main)), + tenantIdentifier.toAppIdentifier(), (StorageLayer.getStorage(tenantIdentifier, + process.main)), user.getSupertokensUserId()); { diff --git a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java index fc775fec1..e86763772 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java @@ -32,8 +32,6 @@ import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -224,7 +222,6 @@ public void testListUsersByAccountInfoForUnlinkedAccountsWithUnionOption() throw JsonObject user3json = getUserById(process.getProcess(), user3.getSupertokensUserId()); JsonObject user4json = getUserById(process.getProcess(), user4.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); { JsonArray users = getUsersByAccountInfo(process.getProcess(), true, "test1@example.com", "+919876543210", null, null); assertEquals(2, users.size()); @@ -269,7 +266,6 @@ public void testUnknownAccountInfo() throws Exception { return; } - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).size()); assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, null, null, "google", "userid1").size()); assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test3@example.com", null, null, null).size()); @@ -300,9 +296,6 @@ public void testListUserByAccountInfoWhenAccountsAreLinked1() throws Exception { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); - JsonObject primaryUserJson = getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, @@ -373,9 +366,6 @@ public void testListUserByAccountInfoWhenAccountsAreLinked3() throws Exception { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); - JsonObject primaryUserJson = getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, @@ -408,9 +398,6 @@ public void testListUserByAccountInfoWhenAccountsAreLinked4() throws Exception { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); - JsonObject primaryUserJson = getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, @@ -445,9 +432,6 @@ public void testListUserByAccountInfoWhenAccountsAreLinked5() throws Exception { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); - JsonObject primaryUserJson = getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, diff --git a/src/test/java/io/supertokens/test/accountlinking/api/SessionTests.java b/src/test/java/io/supertokens/test/accountlinking/api/SessionTests.java index 96ef02b93..ddac8d03e 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/SessionTests.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/SessionTests.java @@ -27,8 +27,6 @@ import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.session.Session; import io.supertokens.session.info.SessionInformationHolder; import io.supertokens.storageLayer.StorageLayer; @@ -122,8 +120,6 @@ public void testGetSessionForUserWithAndWithoutIncludingAllLinkedAccounts() thro new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); - { String[] sessions = getSessionsForUser(process.getProcess(), user1.getSupertokensUserId(), false); @@ -190,9 +186,6 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); - - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); revokeSessionsForUser(process.getProcess(), user1.getSupertokensUserId(), true); try { @@ -217,8 +210,6 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); revokeSessionsForUser(process.getProcess(), user2.getSupertokensUserId(), true); try { @@ -244,8 +235,6 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); revokeSessionsForUser(process.getProcess(), user1.getSupertokensUserId(), null); try { @@ -270,8 +259,6 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); revokeSessionsForUser(process.getProcess(), user2.getSupertokensUserId(), null); try { @@ -296,8 +283,6 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); revokeSessionsForUser(process.getProcess(), user1.getSupertokensUserId(), false); try { @@ -318,8 +303,6 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); - TenantIdentifierWithStorage baseTenant = TenantIdentifier.BASE_TENANT.withStorage( - StorageLayer.getBaseStorage(process.getProcess())); revokeSessionsForUser(process.getProcess(), user2.getSupertokensUserId(), false); Session.getSession(process.getProcess(), session1.session.handle); diff --git a/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java b/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java index 36a0c9f32..267a8f640 100644 --- a/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java @@ -31,6 +31,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; @@ -193,7 +194,7 @@ private void createUsers() } AuthRecipeUserInfo user1 = EmailPassword.signUp( - tenant.withStorage(StorageLayer.getStorage(tenant, process.getProcess())), + tenant, (StorageLayer.getStorage(tenant, process.getProcess())), process.getProcess(), "user@example.com", "password" + (pcount++) @@ -201,7 +202,7 @@ private void createUsers() tenantToUsers.get(tenant).add(user1.getSupertokensUserId()); recipeToUsers.get("emailpassword").add(user1.getSupertokensUserId()); AuthRecipeUserInfo user2 = EmailPassword.signUp( - tenant.withStorage(StorageLayer.getStorage(tenant, process.getProcess())), + tenant, (StorageLayer.getStorage(tenant, process.getProcess())), process.getProcess(), "user@gmail.com", "password2" + (pcount++) @@ -213,7 +214,7 @@ private void createUsers() { // passwordless users recipeToUsers.put("passwordless", new ArrayList<>()); for (TenantIdentifier tenant : new TenantIdentifier[]{t1, t2, t3}) { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenant.withStorage( + Storage storage = ( StorageLayer.getStorage(tenant, process.getProcess())); { if (tenantToUsers.get(tenant) == null) { @@ -221,13 +222,15 @@ private void createUsers() } Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( - tenantIdentifierWithStorage, + tenant, + storage, process.getProcess(), "user@example.com", null, null, "abcd" ); - Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, + Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode( + tenant, storage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); tenantToUsers.get(tenant).add(response.user.getSupertokensUserId()); @@ -235,13 +238,14 @@ private void createUsers() } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( - tenantIdentifierWithStorage, + tenant, storage, process.getProcess(), "user@gmail.com", null, null, "abcd" ); - Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, + Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode( + tenant, storage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); tenantToUsers.get(tenant).add(response.user.getSupertokensUserId()); @@ -249,13 +253,14 @@ private void createUsers() } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( - tenantIdentifierWithStorage, + tenant, storage, process.getProcess(), null, "+1234567890", null, "abcd" ); - Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, + Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode( + tenant, storage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); tenantToUsers.get(tenant).add(response.user.getSupertokensUserId()); @@ -263,13 +268,14 @@ private void createUsers() } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( - tenantIdentifierWithStorage, + tenant, storage, process.getProcess(), null, "+9876543210", null, "abcd" ); - Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, + Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode( + tenant, storage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); tenantToUsers.get(tenant).add(response.user.getSupertokensUserId()); @@ -285,25 +291,29 @@ private void createUsers() tenantToUsers.put(tenant, new ArrayList<>()); } - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenant.withStorage( + Storage storage = ( StorageLayer.getStorage(tenant, process.getProcess())); - ThirdParty.SignInUpResponse user1 = ThirdParty.signInUp(tenantIdentifierWithStorage, + ThirdParty.SignInUpResponse user1 = ThirdParty.signInUp( + tenant, storage, process.getProcess(), "google", "googleid1", "user@example.com"); tenantToUsers.get(tenant).add(user1.user.getSupertokensUserId()); recipeToUsers.get("thirdparty").add(user1.user.getSupertokensUserId()); - ThirdParty.SignInUpResponse user2 = ThirdParty.signInUp(tenantIdentifierWithStorage, + ThirdParty.SignInUpResponse user2 = ThirdParty.signInUp( + tenant, storage, process.getProcess(), "google", "googleid2", "user@gmail.com"); tenantToUsers.get(tenant).add(user2.user.getSupertokensUserId()); recipeToUsers.get("thirdparty").add(user2.user.getSupertokensUserId()); - ThirdParty.SignInUpResponse user3 = ThirdParty.signInUp(tenantIdentifierWithStorage, + ThirdParty.SignInUpResponse user3 = ThirdParty.signInUp( + tenant, storage, process.getProcess(), "facebook", "facebookid1", "user@example.com"); tenantToUsers.get(tenant).add(user3.user.getSupertokensUserId()); recipeToUsers.get("thirdparty").add(user3.user.getSupertokensUserId()); - ThirdParty.SignInUpResponse user4 = ThirdParty.signInUp(tenantIdentifierWithStorage, + ThirdParty.SignInUpResponse user4 = ThirdParty.signInUp( + tenant, storage, process.getProcess(), "facebook", "facebookid2", "user@gmail.com"); tenantToUsers.get(tenant).add(user4.user.getSupertokensUserId()); recipeToUsers.get("thirdparty").add(user4.user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java index 899da3906..d826cd43e 100644 --- a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java @@ -31,6 +31,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; @@ -184,12 +185,12 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String tenantToUsers.put(tenantIdentifier, new ArrayList<>()); } - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage( + Storage storage = ( StorageLayer.getStorage(tenantIdentifier, process.getProcess())); for (int i = 0; i < numUsers; i++) { { AuthRecipeUserInfo user = EmailPassword.signUp( - tenantIdentifierWithStorage, process.getProcess(), + tenantIdentifier, storage, process.getProcess(), prefix + "epuser" + i + "@example.com", "password" + i); tenantToUsers.get(tenantIdentifier).add(user.getSupertokensUserId()); if (!recipeToUsers.containsKey("emailpassword")) { @@ -199,13 +200,14 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( - tenantIdentifierWithStorage, + tenantIdentifier, storage, process.getProcess(), prefix + "pluser" + i + "@example.com", null, null, "abcd" ); - Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, + Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode( + tenantIdentifier, storage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); tenantToUsers.get(tenantIdentifier).add(response.user.getSupertokensUserId()); @@ -216,10 +218,12 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String recipeToUsers.get("passwordless").add(response.user.getSupertokensUserId()); } { - ThirdParty.SignInUpResponse user1 = ThirdParty.signInUp(tenantIdentifierWithStorage, + ThirdParty.SignInUpResponse user1 = ThirdParty.signInUp( + tenantIdentifier, storage, process.getProcess(), "google", "googleid" + i, prefix + "tpuser" + i + "@example.com"); tenantToUsers.get(tenantIdentifier).add(user1.user.getSupertokensUserId()); - ThirdParty.SignInUpResponse user2 = ThirdParty.signInUp(tenantIdentifierWithStorage, + ThirdParty.SignInUpResponse user2 = ThirdParty.signInUp( + tenantIdentifier, storage, process.getProcess(), "facebook", "fbid" + i, prefix + "tpuser" + i + "@example.com"); tenantToUsers.get(tenantIdentifier).add(user2.user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/dashboard/apis/MultitenantAPITest.java b/src/test/java/io/supertokens/test/dashboard/apis/MultitenantAPITest.java index 801652cc1..28f761d3c 100644 --- a/src/test/java/io/supertokens/test/dashboard/apis/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/dashboard/apis/MultitenantAPITest.java @@ -26,6 +26,7 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.*; @@ -172,9 +173,10 @@ public void testSessionBehavior() throws Exception { String email = "test@example.com"; String password = "testPass123"; - AppIdentifierWithStorage appIdentifierWithStorage = t1.toAppIdentifier().withStorage( + Storage appIdentifierStorage = ( StorageLayer.getStorage(t1, process.getProcess())); - Dashboard.signUpDashboardUser(appIdentifierWithStorage, process.getProcess(), email, password); + Dashboard.signUpDashboardUser(t1.toAppIdentifier(), appIdentifierStorage, process.getProcess(), email, + password); // create a session String sessionId; diff --git a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java index 02e5df94a..235a9c72f 100644 --- a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java @@ -29,6 +29,8 @@ import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; @@ -85,14 +87,12 @@ public void testStorageLayerGetMailPasswordStorageLayerThrowsExceptionIfTypeIsNo if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { try { - new TenantIdentifierWithStorage(null, null, null, - StorageLayer.getStorage(process.getProcess())).getEmailPasswordStorage(); + StorageUtils.getEmailPasswordStorage(StorageLayer.getStorage(process.getProcess())); throw new Exception("Should not come here"); } catch (UnsupportedOperationException e) { } } else { - new TenantIdentifierWithStorage(null, null, null, - StorageLayer.getStorage(process.getProcess())).getEmailPasswordStorage(); + StorageUtils.getEmailPasswordStorage(StorageLayer.getStorage(process.getProcess())); } process.kill(); @@ -939,9 +939,9 @@ public void updateEmailSucceedsIfEmailUsedByOtherPrimaryUserInDifferentTenantWhi null, null, new JsonObject())); - TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", - StorageLayer.getStorage(process.main)); - AuthRecipeUserInfo user0 = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + Storage storage = (StorageLayer.getStorage(process.main)); + AuthRecipeUserInfo user0 = EmailPassword.signUp( + new TenantIdentifier(null, null, "t1"), storage, process.getProcess(), "someemail1@gmail.com", "pass1234"); @@ -977,9 +977,9 @@ public void updateEmailFailsIfEmailUsedByOtherPrimaryUserInDifferentTenant() null, null, new JsonObject())); - TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", - StorageLayer.getStorage(process.main)); - AuthRecipeUserInfo user0 = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + Storage storage = (StorageLayer.getStorage(process.main)); + AuthRecipeUserInfo user0 = EmailPassword.signUp( + new TenantIdentifier(null, null, "t1"), storage, process.getProcess(), "someemail1@gmail.com", "pass1234"); @@ -988,7 +988,8 @@ public void updateEmailFailsIfEmailUsedByOtherPrimaryUserInDifferentTenant() AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - Multitenancy.addUserIdToTenant(process.main, tenantIdentifierWithStorage, user.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.main, + new TenantIdentifier(null, null, "t1"), storage, user.getSupertokensUserId()); try { EmailPassword.updateUsersEmailOrPassword(process.main, user.getSupertokensUserId(), "someemail1@gmail.com", null); diff --git a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java index fde572254..64c96c15c 100644 --- a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java @@ -154,29 +154,29 @@ public void testSignUpAndLoginInDifferentTenants() createTenants(process); TenantIdentifier t1 = new TenantIdentifier(null, "a1", null); - TenantIdentifierWithStorage t1storage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + Storage t1storage = (StorageLayer.getStorage(t1, process.getProcess())); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t1"); - TenantIdentifierWithStorage t2storage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t2storage = (StorageLayer.getStorage(t2, process.getProcess())); TenantIdentifier t3 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t3storage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); + Storage t3storage = (StorageLayer.getStorage(t3, process.getProcess())); { - EmailPassword.signUp(t1storage, process.getProcess(), "user1@example.com", "password1"); - AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user1@example.com", + EmailPassword.signUp(t1, t1storage, process.getProcess(), "user1@example.com", "password1"); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1, t1storage, process.getProcess(), "user1@example.com", "password1"); assertEquals("user1@example.com", userInfo.loginMethods[0].email); } { - EmailPassword.signUp(t2storage, process.getProcess(), "user2@example.com", "password2"); - AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user2@example.com", + EmailPassword.signUp(t2, t2storage, process.getProcess(), "user2@example.com", "password2"); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2, t2storage, process.getProcess(), "user2@example.com", "password2"); assertEquals("user2@example.com", userInfo.loginMethods[0].email); } { - EmailPassword.signUp(t3storage, process.getProcess(), "user3@example.com", "password3"); - AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user3@example.com", + EmailPassword.signUp(t3, t3storage, process.getProcess(), "user3@example.com", "password3"); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3, t3storage, process.getProcess(), "user3@example.com", "password3"); assertEquals("user3@example.com", userInfo.loginMethods[0].email); } @@ -206,31 +206,31 @@ public void testSameEmailWithDifferentPasswordsOnDifferentTenantsWorksCorrectly( createTenants(process); TenantIdentifier t1 = new TenantIdentifier(null, "a1", null); - TenantIdentifierWithStorage t1storage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + Storage t1storage = (StorageLayer.getStorage(t1, process.getProcess())); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t1"); - TenantIdentifierWithStorage t2storage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t2storage = (StorageLayer.getStorage(t2, process.getProcess())); TenantIdentifier t3 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t3storage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); + Storage t3storage = (StorageLayer.getStorage(t3, process.getProcess())); - EmailPassword.signUp(t1storage, process.getProcess(), "user@example.com", "password1"); - EmailPassword.signUp(t2storage, process.getProcess(), "user@example.com", "password2"); - EmailPassword.signUp(t3storage, process.getProcess(), "user@example.com", "password3"); + EmailPassword.signUp(t1, t1storage, process.getProcess(), "user@example.com", "password1"); + EmailPassword.signUp(t2, t2storage, process.getProcess(), "user@example.com", "password2"); + EmailPassword.signUp(t3, t3storage, process.getProcess(), "user@example.com", "password3"); { - AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user@example.com", + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1, t1storage, process.getProcess(), "user@example.com", "password1"); assertEquals("user@example.com", userInfo.loginMethods[0].email); } { - AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user@example.com", + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2, t2storage, process.getProcess(), "user@example.com", "password2"); assertEquals("user@example.com", userInfo.loginMethods[0].email); } { - AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user@example.com", + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3, t3storage, process.getProcess(), "user@example.com", "password3"); assertEquals("user@example.com", userInfo.loginMethods[0].email); } @@ -260,39 +260,48 @@ public void testGetUserUsingIdReturnsCorrectUser() createTenants(process); TenantIdentifier t1 = new TenantIdentifier(null, "a1", null); - TenantIdentifierWithStorage t1storage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + Storage t1storage = (StorageLayer.getStorage(t1, process.getProcess())); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t1"); - TenantIdentifierWithStorage t2storage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t2storage = (StorageLayer.getStorage(t2, process.getProcess())); TenantIdentifier t3 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t3storage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); + Storage t3storage = (StorageLayer.getStorage(t3, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1storage, process.getProcess(), "user1@example.com", "password1"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t2storage, process.getProcess(), "user2@example.com", "password2"); - AuthRecipeUserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user3@example.com", "password3"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1, t1storage, process.getProcess(), "user1@example.com", + "password1"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2, t2storage, process.getProcess(), "user2@example.com", + "password2"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(t3, t3storage, process.getProcess(), "user3@example.com", + "password3"); - Storage storage = StorageLayer.getStorage(process.getProcess()); + Storage[] storages = StorageLayer.getStoragesForApp(process.getProcess(), new AppIdentifier(null, "a1")); { AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingId( - StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user1.getSupertokensUserId(), - UserIdType.SUPERTOKENS).appIdentifierWithStorage, user1.getSupertokensUserId()); + new AppIdentifier(null, "a1"), + StorageLayer.findStorageAndUserIdMappingForUser( + new AppIdentifier(null, "a1"), storages, + user1.getSupertokensUserId(), + UserIdType.SUPERTOKENS).storage, user1.getSupertokensUserId()); assertEquals(user1, userInfo); } { AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingId( - StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user2.getSupertokensUserId(), - UserIdType.SUPERTOKENS).appIdentifierWithStorage, user2.getSupertokensUserId()); + new AppIdentifier(null, "a1"), + StorageLayer.findStorageAndUserIdMappingForUser( + new AppIdentifier(null, "a1"), storages, + user2.getSupertokensUserId(), + UserIdType.SUPERTOKENS).storage, user2.getSupertokensUserId()); assertEquals(user2, userInfo); } { AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingId( - StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user3.getSupertokensUserId(), - UserIdType.SUPERTOKENS).appIdentifierWithStorage, user3.getSupertokensUserId()); + new AppIdentifier(null, "a1"), + StorageLayer.findStorageAndUserIdMappingForUser( + new AppIdentifier(null, "a1"), storages, + user3.getSupertokensUserId(), + UserIdType.SUPERTOKENS).storage, user3.getSupertokensUserId()); assertEquals(user3, userInfo); } @@ -320,28 +329,31 @@ public void testGetUserUsingEmailReturnsTheUserFromTheSpecificTenant() createTenants(process); TenantIdentifier t1 = new TenantIdentifier(null, "a1", null); - TenantIdentifierWithStorage t1storage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + Storage t1storage = (StorageLayer.getStorage(t1, process.getProcess())); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t1"); - TenantIdentifierWithStorage t2storage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t2storage = (StorageLayer.getStorage(t2, process.getProcess())); TenantIdentifier t3 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t3storage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); + Storage t3storage = (StorageLayer.getStorage(t3, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1storage, process.getProcess(), "user@example.com", "password1"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t2storage, process.getProcess(), "user@example.com", "password2"); - AuthRecipeUserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user@example.com", "password3"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1, t1storage, process.getProcess(), "user@example.com", + "password1"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2, t2storage, process.getProcess(), "user@example.com", + "password2"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(t3, t3storage, process.getProcess(), "user@example.com", + "password3"); { - AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t1storage, user1.loginMethods[0].email); + AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t1, t1storage, user1.loginMethods[0].email); assertEquals(user1, userInfo); } { - AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t2storage, user2.loginMethods[0].email); + AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t2, t2storage, user2.loginMethods[0].email); assertEquals(user2, userInfo); } { - AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t3storage, user3.loginMethods[0].email); + AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t3, t3storage, user3.loginMethods[0].email); assertEquals(user3, userInfo); } @@ -371,54 +383,65 @@ public void testUpdatePasswordWorksCorrectlyAcrossAllTenants() createTenants(process); TenantIdentifier t1 = new TenantIdentifier(null, "a1", null); - TenantIdentifierWithStorage t1storage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + Storage t1storage = (StorageLayer.getStorage(t1, process.getProcess())); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t1"); - TenantIdentifierWithStorage t2storage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t2storage = (StorageLayer.getStorage(t2, process.getProcess())); TenantIdentifier t3 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t3storage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); + Storage t3storage = (StorageLayer.getStorage(t3, process.getProcess())); - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1storage, process.getProcess(), "user@example.com", "password1"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t2storage, process.getProcess(), "user@example.com", "password2"); - AuthRecipeUserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user@example.com", "password3"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1, t1storage, process.getProcess(), "user@example.com", + "password1"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2, t2storage, process.getProcess(), "user@example.com", + "password2"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(t3, t3storage, process.getProcess(), "user@example.com", + "password3"); - Storage storage = StorageLayer.getStorage(process.getProcess()); + Storage[] storages = StorageLayer.getStoragesForApp(process.getProcess(), new AppIdentifier(null, "a1")); EmailPassword.updateUsersEmailOrPassword( - StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user1.getSupertokensUserId(), - UserIdType.SUPERTOKENS).appIdentifierWithStorage, + new AppIdentifier(null, "a1"), + StorageLayer.findStorageAndUserIdMappingForUser( + new AppIdentifier(null, "a1"), storages, + user1.getSupertokensUserId(), + UserIdType.SUPERTOKENS).storage, process.getProcess(), user1.getSupertokensUserId(), null, "newpassword1"); EmailPassword.updateUsersEmailOrPassword( - StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user2.getSupertokensUserId(), - UserIdType.SUPERTOKENS).appIdentifierWithStorage, + new AppIdentifier(null, "a1"), + StorageLayer.findStorageAndUserIdMappingForUser( + new AppIdentifier(null, "a1"), storages, + user2.getSupertokensUserId(), + UserIdType.SUPERTOKENS).storage, process.getProcess(), user2.getSupertokensUserId(), null, "newpassword2"); EmailPassword.updateUsersEmailOrPassword( - StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user3.getSupertokensUserId(), - UserIdType.SUPERTOKENS).appIdentifierWithStorage, + new AppIdentifier(null, "a1"), + StorageLayer.findStorageAndUserIdMappingForUser( + new AppIdentifier(null, "a1"), storages, + user3.getSupertokensUserId(), + UserIdType.SUPERTOKENS).storage, process.getProcess(), user3.getSupertokensUserId(), null, "newpassword3"); { - t1 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t1, user1.getSupertokensUserId(), - UserIdType.SUPERTOKENS).tenantIdentifierWithStorage; - AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user@example.com", + t1storage = StorageLayer.findStorageAndUserIdMappingForUser(process.getProcess(), t1, user1.getSupertokensUserId(), + UserIdType.SUPERTOKENS).storage; + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1, t1storage, process.getProcess(), "user@example.com", "newpassword1"); assertEquals(user1.getSupertokensUserId(), userInfo.getSupertokensUserId()); } { - t2 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t2, user2.getSupertokensUserId(), - UserIdType.SUPERTOKENS).tenantIdentifierWithStorage; - AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user@example.com", + t2storage = StorageLayer.findStorageAndUserIdMappingForUser(process.getProcess(), t2, + user2.getSupertokensUserId(), + UserIdType.SUPERTOKENS).storage; + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2, t2storage, process.getProcess(), "user@example.com", "newpassword2"); assertEquals(user2.getSupertokensUserId(), userInfo.getSupertokensUserId()); } { - t3 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t3, user3.getSupertokensUserId(), - UserIdType.SUPERTOKENS).tenantIdentifierWithStorage; - AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user@example.com", + t3storage = StorageLayer.findStorageAndUserIdMappingForUser(process.getProcess(), t3, + user3.getSupertokensUserId(), + UserIdType.SUPERTOKENS).storage; + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3, t3storage, process.getProcess(), "user@example.com", "newpassword3"); assertEquals(user3.getSupertokensUserId(), userInfo.getSupertokensUserId()); } diff --git a/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java b/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java index 30735bc4e..94de67df9 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java @@ -389,9 +389,7 @@ public void testGetUserUsingIdReturnsUserFromTheRightTenantWhileQueryingFromAnyT JsonObject user3 = signUp(t3, "user@example.com", "password3"); for (JsonObject user : new JsonObject[]{user1, user2, user3}) { - for (TenantIdentifier t : new TenantIdentifier[]{t1, t2, t3}) { - assertEquals(user, getUserUsingId(t, user.getAsJsonPrimitive("id").getAsString())); - } + assertEquals(user, getUserUsingId(t1, user.getAsJsonPrimitive("id").getAsString())); } } @@ -431,16 +429,14 @@ public void testUpdatePasswordWorksCorrectlyAcrossTenants() throws Exception { JsonObject user = users[i]; TenantIdentifier userTenant = tenants[i]; - for (TenantIdentifier tenant : tenants) { - String newPassword = generateRandomString(16); - updatePassword(tenant, user.getAsJsonPrimitive("id").getAsString(), newPassword); + String newPassword = generateRandomString(16); + updatePassword(t1, user.getAsJsonPrimitive("id").getAsString(), newPassword); - for (TenantIdentifier loginTenant : tenants) { - if (loginTenant.equals(userTenant)) { - assertEquals(user, successfulSignIn(loginTenant, "user@example.com", newPassword)); - } else { - wrongCredentialsSignIn(loginTenant, "user@example.com", newPassword); - } + for (TenantIdentifier loginTenant : tenants) { + if (loginTenant.equals(userTenant)) { + assertEquals(user, successfulSignIn(loginTenant, "user@example.com", newPassword)); + } else { + wrongCredentialsSignIn(loginTenant, "user@example.com", newPassword); } } } @@ -465,18 +461,16 @@ public void testUpdateEmailWorksCorrectlyAcrossTenants() throws Exception { JsonObject user = users[i]; TenantIdentifier userTenant = tenants[i]; - for (TenantIdentifier tenant : tenants) { - String newEmail = (generateRandomString(16) + "@example.com").toLowerCase(); - updateEmail(tenant, user.getAsJsonPrimitive("id").getAsString(), newEmail); - user.remove("email"); - user.addProperty("email", newEmail); + String newEmail = (generateRandomString(16) + "@example.com").toLowerCase(); + updateEmail(t1, user.getAsJsonPrimitive("id").getAsString(), newEmail); + user.remove("email"); + user.addProperty("email", newEmail); - for (TenantIdentifier loginTenant : tenants) { - if (loginTenant.equals(userTenant)) { - assertEquals(user, successfulSignIn(loginTenant, newEmail, "password")); - } else { - wrongCredentialsSignIn(loginTenant, newEmail, "password"); - } + for (TenantIdentifier loginTenant : tenants) { + if (loginTenant.equals(userTenant)) { + assertEquals(user, successfulSignIn(loginTenant, newEmail, "password")); + } else { + wrongCredentialsSignIn(loginTenant, newEmail, "password"); } } } @@ -501,20 +495,18 @@ public void testUpdateEmailAndPasswordWorksCorrectlyAcrossTenants() throws Excep JsonObject user = users[i]; TenantIdentifier userTenant = tenants[i]; - for (TenantIdentifier tenant : tenants) { - String newPassword = generateRandomString(16); - String newEmail = (generateRandomString(16) + "@example.com").toLowerCase(); - updateEmailAndPassword(tenant, user.getAsJsonPrimitive("id").getAsString(), newEmail, newPassword); + String newPassword = generateRandomString(16); + String newEmail = (generateRandomString(16) + "@example.com").toLowerCase(); + updateEmailAndPassword(t1, user.getAsJsonPrimitive("id").getAsString(), newEmail, newPassword); - user.remove("email"); - user.addProperty("email", newEmail); + user.remove("email"); + user.addProperty("email", newEmail); - for (TenantIdentifier loginTenant : tenants) { - if (loginTenant.equals(userTenant)) { - assertEquals(user, successfulSignIn(loginTenant, newEmail, newPassword)); - } else { - wrongCredentialsSignIn(loginTenant, newEmail, newPassword); - } + for (TenantIdentifier loginTenant : tenants) { + if (loginTenant.equals(userTenant)) { + assertEquals(user, successfulSignIn(loginTenant, newEmail, newPassword)); + } else { + wrongCredentialsSignIn(loginTenant, newEmail, newPassword); } } } @@ -692,7 +684,7 @@ public void testImportUsersWorksCorrectlyAcrossTenants() throws Exception { EmailPasswordSQLStorage storage = (EmailPasswordSQLStorage) StorageLayer.getStorage(t2, process.getProcess()); - storage.signUp(t2.withStorage(storage), "userId", email, combinedPasswordHash, timeJoined); + storage.signUp(t2, "userId", email, combinedPasswordHash, timeJoined); successfulSignIn(t2, email, password); wrongCredentialsSignIn(t1, email, password); diff --git a/src/test/java/io/supertokens/test/emailverification/EmailVerificationWithUserIdMappingTest.java b/src/test/java/io/supertokens/test/emailverification/EmailVerificationWithUserIdMappingTest.java index 66b2926e0..94c35f015 100644 --- a/src/test/java/io/supertokens/test/emailverification/EmailVerificationWithUserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/emailverification/EmailVerificationWithUserIdMappingTest.java @@ -144,6 +144,38 @@ public void testUserIdMappingCreationAfterEmailVerificationForPasswordlessUser() AuthRecipeUserInfo userInfo = AuthRecipe.getUserById(process.getProcess(), user.getSupertokensUserId()); assertTrue(userInfo.loginMethods[0].verified); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), "euid", "test@example.com")); + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), "test@example.com")); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUserIdMappingCreationAfterEmailVerificationForPasswordlessUserWithOlderCDIBehaviour() 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; + } + + Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), "test@example.com", null, + null, null); + AuthRecipeUserInfo user = Passwordless.consumeCode(process.getProcess(), code.deviceId, code.deviceIdHash, + code.userInputCode, null, true).user; + + // create mapping + // this should not be allowed + try { + UserIdMapping.createUserIdMapping(process.getProcess(), user.getSupertokensUserId(), "euid", null, false, false); + fail(); + } catch (Exception e) { + assert e.getMessage().contains("UserId is already in use in EmailVerification recipe"); + } + process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } diff --git a/src/test/java/io/supertokens/test/emailverification/api/MultitenantAPITest.java b/src/test/java/io/supertokens/test/emailverification/api/MultitenantAPITest.java index d832b3573..5969995d2 100644 --- a/src/test/java/io/supertokens/test/emailverification/api/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/emailverification/api/MultitenantAPITest.java @@ -217,8 +217,6 @@ public void testSameEmailAcrossDifferentUserPoolNeedsToBeVerifiedSeparately() th verifyEmail(t1, "userid", "test@example.com"); assertTrue(isEmailVerified(t1, "userid", "test@example.com")); - assertFalse(isEmailVerified(t2, "userid", "test@example.com")); - assertFalse(isEmailVerified(t3, "userid", "test@example.com")); } @Test @@ -228,7 +226,6 @@ public void testSameEmailAcrossDifferentTenantButSameUserPoolDoesNotNeedVerifica } verifyEmail(t2, "userid", "test@example.com"); - assertTrue(isEmailVerified(t2, "userid", "test@example.com")); - assertTrue(isEmailVerified(t3, "userid", "test@example.com")); + assertTrue(isEmailVerified(t1, "userid", "test@example.com")); } } diff --git a/src/test/java/io/supertokens/test/mfa/api/CreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/mfa/api/CreatePrimaryUserAPITest.java index 83fe4e834..c53bb1e2b 100644 --- a/src/test/java/io/supertokens/test/mfa/api/CreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/mfa/api/CreatePrimaryUserAPITest.java @@ -457,7 +457,7 @@ public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { ); AuthRecipeUserInfo user = EmailPassword.signUp( - tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, process.main)), + tenantIdentifier, (StorageLayer.getStorage(tenantIdentifier, process.main)), process.getProcess(), "test@example.com", "abcd1234"); JsonObject userObj; @@ -497,7 +497,7 @@ public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { } AuthRecipe.createPrimaryUser(process.main, - tenantIdentifier.toAppIdentifier().withStorage(StorageLayer.getStorage(tenantIdentifier, process.main)), + tenantIdentifier.toAppIdentifier(), (StorageLayer.getStorage(tenantIdentifier, process.main)), user.getSupertokensUserId()); { diff --git a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java index e9a1df208..aec8e8dff 100644 --- a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java +++ b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java @@ -25,6 +25,7 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; @@ -116,11 +117,11 @@ null, null, new JsonObject() null, null, new JsonObject() ), false); - TenantIdentifierWithStorage tWithStorage = t.withStorage( + Storage tStorage = ( StorageLayer.getStorage(t, process.getProcess())); - AuthRecipeUserInfo user = EmailPassword.signUp(tWithStorage, process.getProcess(), "test@example.com", + AuthRecipeUserInfo user = EmailPassword.signUp(t, tStorage, process.getProcess(), "test@example.com", "password"); String userId = user.getSupertokensUserId(); @@ -129,7 +130,7 @@ null, null, new JsonObject() try { UserIdMapping.findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( - tWithStorage.toAppIdentifierWithStorage(), userId, true); + t.toAppIdentifier(), tStorage, userId, true); fail(className); } catch (Exception ignored) { assertTrue(ignored.getMessage().contains("UserId is already in use")); @@ -156,7 +157,7 @@ null, null, new JsonObject() null, null, new JsonObject() ), false); - UserIdMapping.findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed(tWithStorage.toAppIdentifierWithStorage(), + UserIdMapping.findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed(t.toAppIdentifier(), tStorage, userId, true); } } @@ -213,9 +214,9 @@ null, null, new JsonObject() null, null, new JsonObject() ), false); - TenantIdentifierWithStorage appWithStorage = app.withStorage( + Storage appStorage = ( StorageLayer.getStorage(app, process.getProcess())); - TenantIdentifierWithStorage tenantWithStorage = tenant.withStorage( + Storage tenantStorage = ( StorageLayer.getStorage(tenant, process.getProcess())); for (String className : classNames) { @@ -223,29 +224,29 @@ null, null, new JsonObject() continue; } - AuthRecipeUserInfo user = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(app, appStorage, process.getProcess(), "test@example.com", "password"); String userId = user.getSupertokensUserId(); - Multitenancy.addUserIdToTenant(process.getProcess(), tenantWithStorage, userId); + Multitenancy.addUserIdToTenant(process.getProcess(), tenant, tenantStorage, userId); // create entry in nonAuth table - tenantWithStorage.getStorage().addInfoToNonAuthRecipesBasedOnUserId(tenant, className, userId); + tenantStorage.addInfoToNonAuthRecipesBasedOnUserId(tenant, className, userId); try { UserIdMapping.findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( - tenantWithStorage.toAppIdentifierWithStorage(), userId, true); + tenant.toAppIdentifier(), tenantStorage, userId, true); fail(className); } catch (Exception ignored) { assertTrue(ignored.getMessage().contains("UserId is already in use")); } // Disassociate user - Multitenancy.removeUserIdFromTenant(process.getProcess(), tenantWithStorage, userId, null); + Multitenancy.removeUserIdFromTenant(process.getProcess(), tenant, tenantStorage, userId, null); - assertFalse(AuthRecipe.deleteNonAuthRecipeUser(tenantWithStorage, + assertFalse(AuthRecipe.deleteNonAuthRecipeUser(tenant, tenantStorage, userId)); // Nothing deleted indicates that the non auth recipe user data was deleted already - AuthRecipe.deleteUser(appWithStorage.toAppIdentifierWithStorage(), userId); + AuthRecipe.deleteUser(app.toAppIdentifier(), appStorage, userId); } process.kill(); @@ -286,20 +287,20 @@ null, null, new JsonObject() null, null, new JsonObject() ), false); - TenantIdentifierWithStorage appWithStorage = app.withStorage( + Storage appStorage = ( StorageLayer.getStorage(app, process.getProcess())); - TenantIdentifierWithStorage tenantWithStorage = tenant.withStorage( + Storage tenantStorage = ( StorageLayer.getStorage(tenant, process.getProcess())); - AuthRecipeUserInfo user = EmailPassword.signUp(tenantWithStorage, process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(tenant, tenantStorage, process.getProcess(), "test@example.com", "password"); String userId = user.getSupertokensUserId(); Multitenancy.deleteTenant(tenant, process.getProcess()); - Multitenancy.addUserIdToTenant(process.getProcess(), appWithStorage, + Multitenancy.addUserIdToTenant(process.getProcess(), app, appStorage, userId); // user id must be intact to do this - AuthRecipeUserInfo appUser = EmailPassword.getUserUsingId(appWithStorage.toAppIdentifierWithStorage(), userId); + AuthRecipeUserInfo appUser = EmailPassword.getUserUsingId(app.toAppIdentifier(), appStorage, userId); assertNotNull(appUser); assertEquals(userId, appUser.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/multitenant/RequestConnectionUriDomainTest.java b/src/test/java/io/supertokens/test/multitenant/RequestConnectionUriDomainTest.java index f995af0d4..16612be7c 100644 --- a/src/test/java/io/supertokens/test/multitenant/RequestConnectionUriDomainTest.java +++ b/src/test/java/io/supertokens/test/multitenant/RequestConnectionUriDomainTest.java @@ -88,7 +88,11 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, getTenantIdentifierFromRequest(req).getConnectionUriDomain(), resp); + try { + super.sendTextResponse(200, getTenantIdentifier(req).getConnectionUriDomain(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); @@ -159,9 +163,13 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - super.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - super.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + super.getTenantIdentifier(req).getConnectionUriDomain() + "," + + super.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); @@ -285,9 +293,13 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - super.sendTextResponse(200, - super.getTenantIdentifierFromRequest(req).getConnectionUriDomain() + "," + - super.getTenantIdentifierFromRequest(req).getTenantId(), resp); + try { + super.sendTextResponse(200, + super.getTenantIdentifier(req).getConnectionUriDomain() + "," + + super.getTenantIdentifier(req).getTenantId(), resp); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } } }); diff --git a/src/test/java/io/supertokens/test/multitenant/TestAppData.java b/src/test/java/io/supertokens/test/multitenant/TestAppData.java index 45194ebc6..4f2b33f89 100644 --- a/src/test/java/io/supertokens/test/multitenant/TestAppData.java +++ b/src/test/java/io/supertokens/test/multitenant/TestAppData.java @@ -29,6 +29,7 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.*; @@ -115,58 +116,64 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { null, null, new JsonObject() ), false); - TenantIdentifierWithStorage appWithStorage = app.withStorage( + Storage appStorage = ( StorageLayer.getStorage(app, process.getProcess())); - String[] allTableNames = appWithStorage.getStorage().getAllTablesInTheDatabase(); + String[] allTableNames = appStorage.getAllTablesInTheDatabase(); allTableNames = removeStrings(allTableNames, tablesToIgnore); Arrays.sort(allTableNames); // Add all recipe data - AuthRecipeUserInfo epUser = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); - EmailPassword.generatePasswordResetTokenBeforeCdi4_0(appWithStorage, process.getProcess(), epUser.getSupertokensUserId()); + AuthRecipeUserInfo epUser = EmailPassword.signUp(app, appStorage, process.getProcess(), "test@example.com", + "password"); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(app, appStorage, process.getProcess(), + epUser.getSupertokensUserId()); - ThirdParty.SignInUpResponse tpUser = ThirdParty.signInUp(appWithStorage, process.getProcess(), "google", + ThirdParty.SignInUpResponse tpUser = ThirdParty.signInUp(app, appStorage, process.getProcess(), "google", "googleid", "test@example.com"); - Passwordless.CreateCodeResponse code = Passwordless.createCode(appWithStorage, process.getProcess(), + Passwordless.CreateCodeResponse code = Passwordless.createCode(app, appStorage, process.getProcess(), "test@example.com", null, null, null); - Passwordless.ConsumeCodeResponse plUser = Passwordless.consumeCode(appWithStorage, process.getProcess(), + Passwordless.ConsumeCodeResponse plUser = Passwordless.consumeCode(app, appStorage, process.getProcess(), code.deviceId, code.deviceIdHash, code.userInputCode, null); - Passwordless.createCode(appWithStorage, process.getProcess(), "test@example.com", null, null, null); + Passwordless.createCode(app, appStorage, process.getProcess(), "test@example.com", null, null, null); - Dashboard.signUpDashboardUser(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), + Dashboard.signUpDashboardUser(app.toAppIdentifier(), appStorage, process.getProcess(), "user@example.com", "password"); - Dashboard.signInDashboardUser(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), + Dashboard.signInDashboardUser(app.toAppIdentifier(), appStorage, process.getProcess(), "user@example.com", "password"); - String evToken = EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), + String evToken = EmailVerification.generateEmailVerificationToken(app, appStorage, process.getProcess(), epUser.getSupertokensUserId(), epUser.loginMethods[0].email); - EmailVerification.verifyEmail(appWithStorage, evToken); - EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), tpUser.user.getSupertokensUserId(), + EmailVerification.verifyEmail(app, appStorage, evToken); + EmailVerification.generateEmailVerificationToken(app, appStorage, process.getProcess(), + tpUser.user.getSupertokensUserId(), tpUser.user.loginMethods[0].email); - Session.createNewSession(appWithStorage, process.getProcess(), epUser.getSupertokensUserId(), new JsonObject(), new JsonObject()); + Session.createNewSession(app, appStorage, process.getProcess(), epUser.getSupertokensUserId(), + new JsonObject(), new JsonObject()); - UserRoles.createNewRoleOrModifyItsPermissions(appWithStorage.toAppIdentifierWithStorage(), "role", + UserRoles.createNewRoleOrModifyItsPermissions(app.toAppIdentifier(), appStorage, "role", new String[]{"permission1", "permission2"}); - UserRoles.addRoleToUser(appWithStorage, epUser.getSupertokensUserId(), "role"); + UserRoles.addRoleToUser(process.getProcess(), app, appStorage, epUser.getSupertokensUserId(), "role"); - TOTPDevice totpDevice = Totp.registerDevice(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), + TOTPDevice totpDevice = Totp.registerDevice(app.toAppIdentifier(), appStorage, process.getProcess(), epUser.getSupertokensUserId(), "test", 1, 3); - Totp.verifyDevice(appWithStorage, process.getProcess(), epUser.getSupertokensUserId(), totpDevice.deviceName, + Totp.verifyDevice(app, appStorage, process.getProcess(), epUser.getSupertokensUserId(), totpDevice.deviceName, generateTotpCode(process.getProcess(), totpDevice, -1)); - Totp.verifyCode(appWithStorage, process.getProcess(), epUser.getSupertokensUserId(), + Totp.verifyCode(app, appStorage, process.getProcess(), epUser.getSupertokensUserId(), generateTotpCode(process.getProcess(), totpDevice, 0)); - ActiveUsers.updateLastActive(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), epUser.getSupertokensUserId()); + ActiveUsers.updateLastActive(app.toAppIdentifier(), process.getProcess(), + epUser.getSupertokensUserId()); - UserMetadata.updateUserMetadata(appWithStorage.toAppIdentifierWithStorage(), epUser.getSupertokensUserId(), new JsonObject()); + UserMetadata.updateUserMetadata(app.toAppIdentifier(), appStorage, + epUser.getSupertokensUserId(), new JsonObject()); - UserIdMapping.createUserIdMapping(process.getProcess(), appWithStorage.toAppIdentifierWithStorage(), + UserIdMapping.createUserIdMapping(process.getProcess(), app.toAppIdentifier(), appStorage, plUser.user.getSupertokensUserId(), "externalid", null, false); - String[] tablesThatHaveData = appWithStorage.getStorage() + String[] tablesThatHaveData = appStorage .getAllTablesInTheDatabaseThatHasDataForAppId(app.getAppId()); tablesThatHaveData = removeStrings(tablesThatHaveData, tablesToIgnore); Arrays.sort(tablesThatHaveData); @@ -177,7 +184,7 @@ null, null, new JsonObject() Multitenancy.deleteApp(app.toAppIdentifier(), process.getProcess()); // Check no data is remaining in any of the tables - tablesThatHaveData = appWithStorage.getStorage().getAllTablesInTheDatabaseThatHasDataForAppId(app.getAppId()); + tablesThatHaveData = appStorage.getAllTablesInTheDatabaseThatHasDataForAppId(app.getAppId()); tablesThatHaveData = removeStrings(tablesThatHaveData, tablesToIgnore); assertEquals(0, tablesThatHaveData.length); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestApp.java b/src/test/java/io/supertokens/test/multitenant/api/TestApp.java index 36b6fabb9..d771e708d 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestApp.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestApp.java @@ -20,6 +20,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.supertokens.ProcessState; +import io.supertokens.config.CoreConfigTestContent; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; @@ -324,7 +325,8 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { try { - super.sendTextResponse(200, this.getAppIdentifierWithStorage(req).getAppId(), resp); + getTenantStorage(req); + super.sendTextResponse(200, this.getTenantIdentifier(req).getAppId(), resp); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } @@ -510,10 +512,6 @@ public void testDefaultRecipesEnabledWhileCreatingApp() throws Exception { @Test public void testFirstFactorsArray() throws Exception { - if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { - return; - } - JsonObject config = new JsonObject(); StorageLayer.getBaseStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(config, 1); @@ -737,6 +735,78 @@ public void testDuplicateValuesInFirstFactorsAndRequiredSecondaryFactors() throw } } + @Test + public void testInvalidTypedValueInCoreConfigWhileCreatingApp() throws Exception { + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + + String[] properties = new String[]{ + "access_token_validity", // long + "access_token_validity", // long + "access_token_validity", // long + "access_token_validity", // long + "disable_telemetry", // boolean + "postgresql_connection_pool_size", // int + "mysql_connection_pool_size", // int + }; + Object[] values = new Object[]{ + "abcd", // access_token_validity + "", + "null", + null, + "abcd", // disable_telemetry + "abcd", // postgresql_connection_pool_size + "abcd", // mysql_connection_pool_size + }; + + String[] expectedErrorMessages = new String[]{ + "Http error. Status Code: 400. Message: Invalid core config: 'access_token_validity' must be of type long", // access_token_validity + "Http error. Status Code: 400. Message: Invalid core config: 'access_token_validity' must be of type long", // access_token_validity + "Http error. Status Code: 400. Message: Invalid core config: 'access_token_validity' must be of type long", // access_token_validity + null, + "Http error. Status Code: 400. Message: Invalid core config: 'disable_telemetry' must be of type boolean", // disable_telemetry + "Http error. Status Code: 400. Message: Invalid core config: 'postgresql_connection_pool_size' must be of type int", // postgresql_connection_pool_size + "Http error. Status Code: 400. Message: Invalid core config: 'mysql_connection_pool_size' must be of type int", // mysql_connection_pool_size + }; + + System.out.println(StorageLayer.getStorage(process.getProcess()).getClass().getCanonicalName()); + + for (int i = 0; i < properties.length; i++) { + try { + System.out.println("Test case " + i); + JsonObject config = new JsonObject(); + if (values[i] == null) { + config.add(properties[i], null); + } + else if (values[i] instanceof String) { + config.addProperty(properties[i], (String) values[i]); + } else if (values[i] instanceof Boolean) { + config.addProperty(properties[i], (Boolean) values[i]); + } else if (values[i] instanceof Number) { + config.addProperty(properties[i], (Number) values[i]); + } else { + throw new RuntimeException("Invalid type"); + } + StorageLayer.getBaseStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(config, 1); + + JsonObject response = TestMultitenancyAPIHelper.createApp( + process.getProcess(), + new TenantIdentifier(null, null, null), + "a1", null, null, null, + config); + if (expectedErrorMessages[i] != null) { + fail(); + } + } catch (HttpResponseException e) { + assertEquals(400, e.statusCode); + if (!e.getMessage().contains("Invalid config key")) { + assertEquals(expectedErrorMessages[i], e.getMessage()); + } + } + } + } + @Test public void testFirstFactorArrayValueValidationBasedOnDisabledRecipe() throws Exception { if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { @@ -1032,6 +1102,32 @@ public void testRequiredSecondaryFactorArrayValueValidationBasedOnDisabledRecipe assertEquals("Http error. Status Code: 400. Message: Invalid core config: requiredSecondaryFactors should not contain 'thirdparty' because thirdParty is disabled for the tenant.", e.getMessage()); } } + } + + public void testInvalidCoreConfig() throws Exception { + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + CoreConfigTestContent.getInstance(process.getProcess()).setKeyValue(CoreConfigTestContent.VALIDITY_TESTING, + true); + + { + JsonObject config = new JsonObject(); + config.addProperty("access_token_validity", 3600); + config.addProperty("refresh_token_validity", 3); + StorageLayer.getBaseStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(config, 1); + try { + JsonObject response = TestMultitenancyAPIHelper.createApp( + process.getProcess(), + new TenantIdentifier(null, null, null), + "a1", null, null, null, + config); + fail(); + } catch (HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Invalid core config: 'refresh_token_validity' must be strictly greater than 'access_token_validity'.", e.getMessage()); + } + } } } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestConnectionUriDomain.java b/src/test/java/io/supertokens/test/multitenant/api/TestConnectionUriDomain.java index 75ffcf498..31102bb5e 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestConnectionUriDomain.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestConnectionUriDomain.java @@ -385,7 +385,8 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { try { - super.sendTextResponse(200, this.getAppIdentifierWithStorage(req).getConnectionUriDomain(), resp); + getTenantStorage(req); + super.sendTextResponse(200, this.getTenantIdentifier(req).getConnectionUriDomain(), resp); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestLicenseBehaviour.java b/src/test/java/io/supertokens/test/multitenant/api/TestLicenseBehaviour.java index 09cc22754..ffcfd7964 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestLicenseBehaviour.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestLicenseBehaviour.java @@ -99,7 +99,7 @@ public void testAllowLicenseRemovalForCoreWithMultitenancy() throws Exception { // Sign up and get user info JsonObject userInfo = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", "password", process.getProcess()); - JsonObject userInfo2 = TestMultitenancyAPIHelper.getEpUserById(new TenantIdentifier(null, "a1", "t1"), + JsonObject userInfo2 = TestMultitenancyAPIHelper.getEpUserById(new TenantIdentifier(null, "a1", null), userInfo.get("id").getAsString(), process.getProcess()); assertEquals(userInfo, userInfo2); } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java b/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java index 0e9a52e14..6a836486a 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java @@ -19,6 +19,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.ThirdPartyConfig; import io.supertokens.test.httpRequest.HttpRequestForTesting; @@ -585,4 +586,124 @@ public static void createUserIdMapping(TenantIdentifier tenantIdentifier, String SemVer.v3_0.get(), "useridmapping"); assertEquals("OK", response.get("status").getAsString()); } + + public static JsonObject getUserById(TenantIdentifier tenantIdentifier, String userId, Main main) + throws HttpResponseException, IOException { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/user/id"), + params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + return response; + } + + public static JsonObject updateUserMetadata(TenantIdentifier tenantIdentifier, String userId, JsonObject metadata, + Main main) + throws HttpResponseException, IOException { + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", userId); + requestBody.add("metadataUpdate", metadata); + JsonObject resp = HttpRequestForTesting.sendJsonPUTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/user/metadata"), + requestBody, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), "usermetadata"); + return resp; + } + + public static JsonObject removeMetadata(TenantIdentifier tenantIdentifier, String userId, Main main) + throws HttpResponseException, IOException { + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", userId); + JsonObject resp = HttpRequestForTesting.sendJsonPOSTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/user/metadata/remove"), + requestBody, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), "usermetadata"); + + return resp; + } + + public static void createRole(TenantIdentifier tenantIdentifier, String role, Main main) + throws HttpResponseException, IOException { + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("role", role); + JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/role"), + requestBody, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), + "userroles"); + assertEquals("OK", response.get("status").getAsString()); + } + + public static void addRoleToUser(TenantIdentifier tenantIdentifier, String userId, String role, Main main) + throws HttpResponseException, IOException { + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("role", role); + requestBody.addProperty("userId", userId); + + JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier,"/recipe/user/role"), requestBody, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), "userroles"); + + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + } + + public static JsonObject getUserRoles(TenantIdentifier tenantIdentifier, String userId, Main main) + throws HttpResponseException, IOException { + HashMap QUERY_PARAMS = new HashMap<>(); + QUERY_PARAMS.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier,"/recipe/user/roles"), QUERY_PARAMS, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), "userroles"); + return response; + } + + public static void deleteRole(TenantIdentifier tenantIdentifier, String role, Main main) + throws HttpResponseException, IOException { + JsonObject request = new JsonObject(); + request.addProperty("role", role); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier,"/recipe/role/remove"), request, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), "userroles"); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + } + + public static void verifyEmail(TenantIdentifier tenantIdentifier, String userId, String email, Main main) + throws HttpResponseException, IOException { + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", userId); + requestBody.addProperty("email", email); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier,"/recipe/user/email/verify/token"), + requestBody, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), "emailverification"); + + assertEquals(response.entrySet().size(), 2); + assertEquals(response.get("status").getAsString(), "OK"); + + JsonObject verifyResponseBody = new JsonObject(); + verifyResponseBody.addProperty("method", "token"); + verifyResponseBody.addProperty("token", response.get("token").getAsString()); + + JsonObject response2 = HttpRequestForTesting.sendJsonPOSTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/user/email/verify"), verifyResponseBody, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), "emailverification"); + + assertEquals(response2.entrySet().size(), 3); + assertEquals(response2.get("status").getAsString(), "OK"); + } + + public static void unverifyEmail(TenantIdentifier tenantIdentifier, String userId, String email, Main main) + throws HttpResponseException, IOException { + JsonObject body = new JsonObject(); + body.addProperty("userId", userId); + body.addProperty("email", email); + + HttpRequestForTesting.sendJsonPOSTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier,"/recipe/user/email/verify/remove"), body, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), RECIPE_ID.EMAIL_VERIFICATION.toString()); + } } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestPermissionChecks.java b/src/test/java/io/supertokens/test/multitenant/api/TestPermissionChecks.java index e2f9e4a32..ebea6ab5d 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestPermissionChecks.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestPermissionChecks.java @@ -218,15 +218,15 @@ public void testPermissionsForListTenants() throws Exception { TestCase[] testCases = new TestCase[]{ new TestCase( new TenantIdentifier("127.0.0.1", "a1", "t1"), null, - "Only the public tenantId is allowed to list all tenants associated with this app" + "Only public tenantId can call this app specific API" ), new TestCase( new TenantIdentifier("127.0.0.1", null, "t1"), null, - "Only the public tenantId is allowed to list all tenants associated with this app" + "Only public tenantId can call this app specific API" ), new TestCase( new TenantIdentifier(null, null, "t1"), null, - "Only the public tenantId is allowed to list all tenants associated with this app" + "Only public tenantId can call this app specific API" ), new TestCase( new TenantIdentifier(null, null, null), null, null @@ -1022,32 +1022,32 @@ public void testPermissionsForDeleteTenant() throws Exception { new TestCase( new TenantIdentifier(null, null, "t1"), new TenantIdentifier(null, null, "t2"), - "Only the public tenantId is allowed to delete a tenant" + "Only public tenantId can call this app specific API" ), new TestCase( new TenantIdentifier(null, null, "t1"), new TenantIdentifier(null, null, "t1"), - "Only the public tenantId is allowed to delete a tenant" + "Only public tenantId can call this app specific API" ), new TestCase( new TenantIdentifier(null, "a1", "t1"), new TenantIdentifier(null, "a1", "t2"), - "Only the public tenantId is allowed to delete a tenant" + "Only public tenantId can call this app specific API" ), new TestCase( new TenantIdentifier(null, "a1", "t1"), new TenantIdentifier(null, "a1", "t1"), - "Only the public tenantId is allowed to delete a tenant" + "Only public tenantId can call this app specific API" ), new TestCase( new TenantIdentifier("127.0.0.1", "a1", "t1"), new TenantIdentifier("127.0.0.1", "a1", "t2"), - "Only the public tenantId is allowed to delete a tenant" + "Only public tenantId can call this app specific API" ), new TestCase( new TenantIdentifier("127.0.0.1", "a1", "t1"), new TenantIdentifier("127.0.0.1", "a1", "t1"), - "Only the public tenantId is allowed to delete a tenant" + "Only public tenantId can call this app specific API" ), new TestCase( new TenantIdentifier(null, null, null), diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenant.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenant.java index bbcdb8dbf..60741ee52 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenant.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenant.java @@ -273,7 +273,8 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { try { - super.sendTextResponse(200, this.getTenantIdentifierWithStorageFromRequest(req).getTenantId(), resp); + getTenantStorage(req); + super.sendTextResponse(200, this.getTenantIdentifier(req).getTenantId(), resp); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java index d913919f6..8c97dd948 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java @@ -32,6 +32,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; @@ -281,12 +282,12 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String tenantToUsers.put(tenantIdentifier, new ArrayList<>()); } - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage( + Storage storage = ( StorageLayer.getStorage(tenantIdentifier, process.getProcess())); for (int i = 0; i < numUsers; i++) { { AuthRecipeUserInfo user = EmailPassword.signUp( - tenantIdentifierWithStorage, process.getProcess(), + tenantIdentifier, storage, process.getProcess(), prefix + "epuser" + i + "@example.com", "password" + i); tenantToUsers.get(tenantIdentifier).add(user.getSupertokensUserId()); if (!recipeToUsers.containsKey("emailpassword")) { @@ -296,13 +297,15 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( - tenantIdentifierWithStorage, + tenantIdentifier, + storage, process.getProcess(), prefix + "pluser" + i + "@example.com", null, null, "abcd" ); - Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, + Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode( + tenantIdentifier, storage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); tenantToUsers.get(tenantIdentifier).add(response.user.getSupertokensUserId()); @@ -313,10 +316,12 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String recipeToUsers.get("passwordless").add(response.user.getSupertokensUserId()); } { - ThirdParty.SignInUpResponse user1 = ThirdParty.signInUp(tenantIdentifierWithStorage, + ThirdParty.SignInUpResponse user1 = ThirdParty.signInUp( + tenantIdentifier, storage, process.getProcess(), "google", "googleid" + i, prefix + "tpuser" + i + "@example.com"); tenantToUsers.get(tenantIdentifier).add(user1.user.getSupertokensUserId()); - ThirdParty.SignInUpResponse user2 = ThirdParty.signInUp(tenantIdentifierWithStorage, + ThirdParty.SignInUpResponse user2 = ThirdParty.signInUp( + tenantIdentifier, storage, process.getProcess(), "facebook", "fbid" + i, prefix + "tpuser" + i + "@example.com"); tenantToUsers.get(tenantIdentifier).add(user2.user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index 0b2dcc3a7..cf7457e6d 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -29,12 +29,12 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.jwt.JWTRecipeStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; import io.supertokens.pluginInterface.usermetadata.UserMetadataStorage; @@ -276,23 +276,24 @@ public void testEmailPasswordUsersHaveTenantIds() throws Exception { TenantIdentifier t1 = new TenantIdentifier(null, "a1", "t1"); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - AuthRecipeUserInfo user = EmailPassword.signUp(t1WithStorage, + AuthRecipeUserInfo user = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "user@example.com", "password"); assertArrayEquals(new String[]{"t1"}, user.tenantIds.toArray()); - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user.getSupertokensUserId()); - user = EmailPassword.getUserUsingId(t1WithStorage.toAppIdentifierWithStorage(), user.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), t2, t2Storage, user.getSupertokensUserId()); + user = EmailPassword.getUserUsingId(t1.toAppIdentifier(), t1Storage, user.getSupertokensUserId()); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - user = EmailPassword.getUserUsingEmail(t1WithStorage, user.loginMethods[0].email); + user = EmailPassword.getUserUsingEmail(t1, t1Storage, user.loginMethods[0].email); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, user.getSupertokensUserId(), null); - user = EmailPassword.getUserUsingId(t1WithStorage.toAppIdentifierWithStorage(), user.getSupertokensUserId()); + Multitenancy.removeUserIdFromTenant(process.getProcess(), t1, t1Storage, user.getSupertokensUserId(), + null); + user = EmailPassword.getUserUsingId(t1.toAppIdentifier(), t1Storage, user.getSupertokensUserId()); assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @@ -307,26 +308,30 @@ public void testPasswordlessUsersHaveTenantIds1() throws Exception { TenantIdentifier t1 = new TenantIdentifier(null, "a1", "t1"); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(t1WithStorage, + Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(t1, t1Storage, process.getProcess(), "user@example.com", null, null, null); - Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1WithStorage, + Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1, t1Storage, process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds.toArray()); AuthRecipeUserInfo user; - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.getSupertokensUserId()); - user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), t2, t2Storage, + consumeCodeResponse.user.getSupertokensUserId()); + user = Passwordless.getUserById(t1.toAppIdentifier(), t1Storage, + consumeCodeResponse.user.getSupertokensUserId()); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - user = Passwordless.getUserByEmail(t1WithStorage, consumeCodeResponse.user.loginMethods[0].email); + user = Passwordless.getUserByEmail(t1, t1Storage, consumeCodeResponse.user.loginMethods[0].email); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.getSupertokensUserId(), null); - user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.getSupertokensUserId()); + Multitenancy.removeUserIdFromTenant(process.getProcess(), t1, t1Storage, + consumeCodeResponse.user.getSupertokensUserId(), null); + user = Passwordless.getUserById(t1.toAppIdentifier(), t1Storage, + consumeCodeResponse.user.getSupertokensUserId()); assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @@ -341,26 +346,30 @@ public void testPasswordlessUsersHaveTenantIds2() throws Exception { TenantIdentifier t1 = new TenantIdentifier(null, "a1", "t1"); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(t1WithStorage, + Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(t1, t1Storage, process.getProcess(), null, "+919876543210", null, null); - Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1WithStorage, + Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1, t1Storage, process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds.toArray()); AuthRecipeUserInfo user; - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.getSupertokensUserId()); - user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), t2, t2Storage, + consumeCodeResponse.user.getSupertokensUserId()); + user = Passwordless.getUserById(t1.toAppIdentifier(), t1Storage, + consumeCodeResponse.user.getSupertokensUserId()); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - user = Passwordless.getUserByPhoneNumber(t1WithStorage, consumeCodeResponse.user.loginMethods[0].phoneNumber); + user = Passwordless.getUserByPhoneNumber(t1, t1Storage, + consumeCodeResponse.user.loginMethods[0].phoneNumber); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.getSupertokensUserId(), null); - user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.getSupertokensUserId()); + Multitenancy.removeUserIdFromTenant(process.getProcess(), t1, t1Storage, + consumeCodeResponse.user.getSupertokensUserId(), null); + user = Passwordless.getUserById(t1.toAppIdentifier(), t1Storage, consumeCodeResponse.user.getSupertokensUserId()); assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @@ -375,30 +384,32 @@ public void testThirdPartyUsersHaveTenantIds() throws Exception { TenantIdentifier t1 = new TenantIdentifier(null, "a1", "t1"); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(t1WithStorage, process.getProcess(), + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(t1, t1Storage, process.getProcess(), "google", "googleid", "user@example.com"); assertArrayEquals(new String[]{"t1"}, signInUpResponse.user.tenantIds.toArray()); - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, signInUpResponse.user.getSupertokensUserId()); + Multitenancy.addUserIdToTenant(process.getProcess(), t2, t2Storage, + signInUpResponse.user.getSupertokensUserId()); AuthRecipeUserInfo user = ThirdParty.getUser( - t1WithStorage.toAppIdentifierWithStorage(), signInUpResponse.user.getSupertokensUserId()); + t1.toAppIdentifier(), t1Storage, signInUpResponse.user.getSupertokensUserId()); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - user = ThirdParty.getUsersByEmail(t1WithStorage, signInUpResponse.user.loginMethods[0].email)[0]; + user = ThirdParty.getUsersByEmail(t1, t1Storage, signInUpResponse.user.loginMethods[0].email)[0]; Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - user = ThirdParty.getUser(t1WithStorage, "google", "googleid"); + user = ThirdParty.getUser(t1, t1Storage, "google", "googleid"); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - user = ThirdParty.getUser(t2WithStorage, "google", "googleid"); + user = ThirdParty.getUser(t2, t2Storage, "google", "googleid"); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, signInUpResponse.user.getSupertokensUserId(), null); - user = ThirdParty.getUser(t1WithStorage.toAppIdentifierWithStorage(), signInUpResponse.user.getSupertokensUserId()); + Multitenancy.removeUserIdFromTenant(process.getProcess(), t1, t1Storage, + signInUpResponse.user.getSupertokensUserId(), null); + user = ThirdParty.getUser(t1.toAppIdentifier(), t1Storage, signInUpResponse.user.getSupertokensUserId()); assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @@ -430,7 +441,7 @@ public void testThatDisassociateUserWithUseridMappingFromWrongTenantDoesNotWork( "password", process.getProcess()); String userId = user.get("id").getAsString(); - TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", "t1"), userId, "externalid", + TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", null), userId, "externalid", process.getProcess()); JsonObject response = TestMultitenancyAPIHelper.disassociateUserFromTenant( @@ -450,7 +461,7 @@ public void testAssociateAndDisassociateWithUseridMapping() throws Exception { "password", process.getProcess()); String userId = user.get("id").getAsString(); - TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", "t1"), userId, "externalid", + TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", null), userId, "externalid", process.getProcess()); JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), @@ -476,7 +487,7 @@ public void testDisassociateUserWithUserIdMappingAndSession() throws Exception { "password", process.getProcess()); String userId = user.get("id").getAsString(); - TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", "t1"), userId, "externalid", + TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", null), userId, "externalid", process.getProcess()); JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), @@ -485,9 +496,9 @@ public void testDisassociateUserWithUserIdMappingAndSession() throws Exception { assertFalse(response.get("wasAlreadyAssociated").getAsBoolean()); TenantIdentifier t2 = new TenantIdentifier(null, "a1", "t2"); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + Storage t2Storage = (StorageLayer.getStorage(t2, process.getProcess())); - SessionInformationHolder session = Session.createNewSession(t2WithStorage, + SessionInformationHolder session = Session.createNewSession(t2, t2Storage, process.getProcess(), "externalid", new JsonObject(), new JsonObject()); response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t2"), @@ -496,7 +507,7 @@ public void testDisassociateUserWithUserIdMappingAndSession() throws Exception { assertTrue(response.get("wasAssociated").getAsBoolean()); try { - Session.getSession(t2WithStorage, session.session.handle); + Session.getSession(t2, t2Storage, session.session.handle); fail(); } catch (UnauthorisedException e) { // OK diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java b/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java new file mode 100644 index 000000000..7798ce2bf --- /dev/null +++ b/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2024, 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.multitenant.api; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; +import io.supertokens.pluginInterface.exceptions.InvalidConfigException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.usermetadata.sqlStorage.UserMetadataSQLStorage; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpResponseException; +import io.supertokens.thirdparty.InvalidProviderConfigException; +import io.supertokens.useridmapping.UserIdMapping; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.*; + +public class TestWithNonAuthRecipes { + TestingProcessManager.TestingProcess process; + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @After + public void afterEach() throws InterruptedException { + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Before + public void beforeEach() throws InterruptedException, InvalidProviderConfigException, + StorageQueryException, FeatureNotEnabledException, TenantOrAppNotFoundException, IOException, + InvalidConfigException, CannotModifyBaseConfigException, BadPermissionException, HttpResponseException { + Utils.reset(); + + String[] args = {"../"}; + + this.process = TestingProcessManager.start(args); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getBaseStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + + JsonObject config = new JsonObject(); + StorageLayer.getBaseStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(config, 1); + TestMultitenancyAPIHelper.createTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, "t1", true, true, + true, config); + StorageLayer.getBaseStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(config, 2); + TestMultitenancyAPIHelper.createTenant(process.getProcess(), TenantIdentifier.BASE_TENANT, "t2", true, true, + true, config); + } + + @Test + public void testThatUserMetadataIsSavedInTheStorageWhereUserExists() 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(t0Storage, new AuthRecipeUserInfo[]{user1}); + UserIdMapping.populateExternalUserIdForUsers(t1Storage, new AuthRecipeUserInfo[]{user2}); + + // Check that get user by ID works fine + JsonObject jsonUser1 = TestMultitenancyAPIHelper.getUserById(t0, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(user1.toJson(), jsonUser1.get("user").getAsJsonObject()); + + JsonObject jsonUser2 = TestMultitenancyAPIHelper.getUserById(t0, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(user2.toJson(), jsonUser2.get("user").getAsJsonObject()); + + JsonObject metadata = new JsonObject(); + metadata.addProperty("key", "value"); + + { + // Add metadata for user2 using t0 and ensure get user works fine + TestMultitenancyAPIHelper.updateUserMetadata(t0, user2.getSupertokensUserId(), metadata, process.getProcess()); + + jsonUser2 = TestMultitenancyAPIHelper.getUserById(t0, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(user2.toJson(), jsonUser2.get("user").getAsJsonObject()); + + try { + TestMultitenancyAPIHelper.getUserById(t1, user2.getSupertokensUserId(), + process.getProcess()); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + } + + { // Add metadata using t1 results in 403 + try { + TestMultitenancyAPIHelper.updateUserMetadata(t1, user1.getSupertokensUserId(), metadata, process.getProcess()); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + } + + { + // Add metadata for user1 using t0 and ensure get user works fine + TestMultitenancyAPIHelper.updateUserMetadata(t0, user1.getSupertokensUserId(), metadata, process.getProcess()); + + jsonUser1 = TestMultitenancyAPIHelper.getUserById(t0, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(user1.toJson(), jsonUser1.get("user").getAsJsonObject()); + + try { + TestMultitenancyAPIHelper.getUserById(t1, user1.getSupertokensUserId(), process.getProcess()); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + } + + UserMetadataSQLStorage t0UserMetadataStorage = StorageUtils.getUserMetadataStorage(t0Storage); + UserMetadataSQLStorage t1UserMetadataStorage = StorageUtils.getUserMetadataStorage(t1Storage); + + // Ensure that the metadata is saved in the correct storage + assertNotNull(t0UserMetadataStorage.getUserMetadata(t0.toAppIdentifier(), user1.getSupertokensUserId())); // ensure t0 storage does not have user2's metadata + assertNotNull(t1UserMetadataStorage.getUserMetadata(t0.toAppIdentifier(), user2.getSupertokensUserId())); // ensure t1 storage does not have user1's metadata + + // Ensure that the metadata is not stored in the wrong storage + assertNull(t0UserMetadataStorage.getUserMetadata(t0.toAppIdentifier(), user2.getSupertokensUserId())); // ensure t0 storage does not have user2's metadata + assertNull(t1UserMetadataStorage.getUserMetadata(t0.toAppIdentifier(), user1.getSupertokensUserId())); // ensure t1 storage does not have user1's metadata + + // Try deleting metadata + try { + TestMultitenancyAPIHelper.removeMetadata(t1, user1.getSupertokensUserId(), process.getProcess()); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + TestMultitenancyAPIHelper.removeMetadata(t0, user1.getSupertokensUserId(), process.getProcess()); + TestMultitenancyAPIHelper.removeMetadata(t0, user2.getSupertokensUserId(), process.getProcess()); + assertNull(t0UserMetadataStorage.getUserMetadata(t0.toAppIdentifier(), user1.getSupertokensUserId())); // ensure t0 storage does not have user2's metadata + assertNull(t1UserMetadataStorage.getUserMetadata(t0.toAppIdentifier(), user2.getSupertokensUserId())); // ensure t1 storage does not have user1's metadata + } + + @Test + public void testThatRoleIsStoredInPublicTenantAndUserRoleMappingInTheUserTenantStorage() 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(t0Storage, new AuthRecipeUserInfo[]{user1}); + UserIdMapping.populateExternalUserIdForUsers(t1Storage, new AuthRecipeUserInfo[]{user2}); + + { + // Check that get user by ID works fine + JsonObject jsonUser1 = TestMultitenancyAPIHelper.getUserById(t0, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(user1.toJson(), jsonUser1.get("user").getAsJsonObject()); + + JsonObject jsonUser2 = TestMultitenancyAPIHelper.getUserById(t0, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(user2.toJson(), jsonUser2.get("user").getAsJsonObject()); + } + + TestMultitenancyAPIHelper.createRole(t0, "role1", process.getProcess()); + + try { + TestMultitenancyAPIHelper.createRole(t1, "role2", process.getProcess()); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + TestMultitenancyAPIHelper.createRole(t0, "role2", process.getProcess()); + + TestMultitenancyAPIHelper.addRoleToUser(t0, user1.getSupertokensUserId(), "role1", process.getProcess()); + TestMultitenancyAPIHelper.addRoleToUser(t1, user2.getSupertokensUserId(), "role2", process.getProcess()); + + { + // Check that get user by ID works fine + JsonObject jsonUser1 = TestMultitenancyAPIHelper.getUserById(t0, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(user1.toJson(), jsonUser1.get("user").getAsJsonObject()); + + JsonObject jsonUser2 = TestMultitenancyAPIHelper.getUserById(t0, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(user2.toJson(), jsonUser2.get("user").getAsJsonObject()); + } + + { + JsonObject user1Roles = TestMultitenancyAPIHelper.getUserRoles(t0, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(1, user1Roles.get("roles").getAsJsonArray().size()); + user1Roles = TestMultitenancyAPIHelper.getUserRoles(t1, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(0, user1Roles.get("roles").getAsJsonArray().size()); + + JsonObject user2Roles = TestMultitenancyAPIHelper.getUserRoles(t0, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(0, user2Roles.get("roles").getAsJsonArray().size()); + user2Roles = TestMultitenancyAPIHelper.getUserRoles(t1, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(1, user2Roles.get("roles").getAsJsonArray().size()); + } + + try { + TestMultitenancyAPIHelper.deleteRole(t1, "role1", process.getProcess()); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + + TestMultitenancyAPIHelper.deleteRole(t0, "role1", process.getProcess()); + TestMultitenancyAPIHelper.deleteRole(t0, "role2", process.getProcess()); + + { + JsonObject user1Roles = TestMultitenancyAPIHelper.getUserRoles(t0, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(0, user1Roles.get("roles").getAsJsonArray().size()); + user1Roles = TestMultitenancyAPIHelper.getUserRoles(t1, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(0, user1Roles.get("roles").getAsJsonArray().size()); + + JsonObject user2Roles = TestMultitenancyAPIHelper.getUserRoles(t0, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(0, user2Roles.get("roles").getAsJsonArray().size()); + user2Roles = TestMultitenancyAPIHelper.getUserRoles(t1, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(0, user2Roles.get("roles").getAsJsonArray().size()); + } + + { + // Check that get user by ID works fine + JsonObject jsonUser1 = TestMultitenancyAPIHelper.getUserById(t0, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(user1.toJson(), jsonUser1.get("user").getAsJsonObject()); + + JsonObject jsonUser2 = TestMultitenancyAPIHelper.getUserById(t0, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(user2.toJson(), jsonUser2.get("user").getAsJsonObject()); + } + } + + @Test + public void testEmailVerificationWithUsersOnDifferentTenantStorages() 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(t0Storage, new AuthRecipeUserInfo[]{user1}); + UserIdMapping.populateExternalUserIdForUsers(t1Storage, new AuthRecipeUserInfo[]{user2}); + + // Check that get user by ID works fine + JsonObject jsonUser1 = TestMultitenancyAPIHelper.getUserById(t0, user1.getSupertokensUserId(), process.getProcess()); + assertEquals(user1.toJson(), jsonUser1.get("user").getAsJsonObject()); + + JsonObject jsonUser2 = TestMultitenancyAPIHelper.getUserById(t0, user2.getSupertokensUserId(), process.getProcess()); + assertEquals(user2.toJson(), jsonUser2.get("user").getAsJsonObject()); + + { + // Add email verification for user2 using t1 and ensure get user works fine + TestMultitenancyAPIHelper.verifyEmail(t1, user2.getSupertokensUserId(), "test@example.com", process.getProcess()); + + jsonUser2 = TestMultitenancyAPIHelper.getUserById(t0, user2.getSupertokensUserId(), process.getProcess()); + user2.loginMethods[0].setVerified(); + assertEquals(user2.toJson(), jsonUser2.get("user").getAsJsonObject()); + + try { + TestMultitenancyAPIHelper.getUserById(t1, user2.getSupertokensUserId(), + process.getProcess()); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + } + + { + // Add email verification for user1 using t0 and ensure get user works fine + TestMultitenancyAPIHelper.verifyEmail(t0, user1.getSupertokensUserId(), "test@example.com", process.getProcess()); + + jsonUser1 = TestMultitenancyAPIHelper.getUserById(t0, user1.getSupertokensUserId(), process.getProcess()); + user1.loginMethods[0].setVerified(); + assertEquals(user1.toJson(), jsonUser1.get("user").getAsJsonObject()); + + try { + TestMultitenancyAPIHelper.getUserById(t1, user1.getSupertokensUserId(), process.getProcess()); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + } + + EmailVerificationSQLStorage t0EvStorage = StorageUtils.getEmailVerificationStorage(t0Storage); + EmailVerificationSQLStorage t1EvStorage = StorageUtils.getEmailVerificationStorage(t1Storage); + + // Ensure that the ev is saved in the correct storage + assertTrue(t0EvStorage.isEmailVerified(t0.toAppIdentifier(), user1.getSupertokensUserId(), "test@example.com")); + assertTrue(t1EvStorage.isEmailVerified(t0.toAppIdentifier(), user2.getSupertokensUserId(), "test@example.com")); + + // Ensure that the metadata is not stored in the wrong storage + assertFalse(t0EvStorage.isEmailVerified(t0.toAppIdentifier(), user2.getSupertokensUserId(), "test@example.com")); // ensure t0 storage does not have user2's ev + assertFalse(t1EvStorage.isEmailVerified(t0.toAppIdentifier(), user1.getSupertokensUserId(), "test@example.com")); // ensure t1 storage does not have user1's ev + + // Try unverify + try { + TestMultitenancyAPIHelper.unverifyEmail(t1, user1.getSupertokensUserId(), "test@example.com", process.getProcess()); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } + TestMultitenancyAPIHelper.unverifyEmail(t0, user1.getSupertokensUserId(), "test@example.com", process.getProcess()); + TestMultitenancyAPIHelper.unverifyEmail(t0, user2.getSupertokensUserId(), "test@example.com", process.getProcess()); + 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 + } +} diff --git a/src/test/java/io/supertokens/test/passwordless/api/MultitenantAPITest.java b/src/test/java/io/supertokens/test/passwordless/api/MultitenantAPITest.java index 54c698899..028e0dae1 100644 --- a/src/test/java/io/supertokens/test/passwordless/api/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/passwordless/api/MultitenantAPITest.java @@ -475,11 +475,9 @@ public void testGetUserUsingIdReturnsUserFromTheRightTenantWhileQueryingFromAnyT JsonObject user2 = signInUpEmailUsingLinkCode(t2, "user1@example.com"); JsonObject user3 = signInUpEmailUsingLinkCode(t3, "user1@example.com"); - for (TenantIdentifier tenant : new TenantIdentifier[]{t1, t2, t3}) { - assertEquals(user1, getUserUsingId(tenant, user1.get("id").getAsString())); - assertEquals(user2, getUserUsingId(tenant, user2.get("id").getAsString())); - assertEquals(user3, getUserUsingId(tenant, user3.get("id").getAsString())); - } + assertEquals(user1, getUserUsingId(t1, user1.get("id").getAsString())); + assertEquals(user2, getUserUsingId(t1, user2.get("id").getAsString())); + assertEquals(user3, getUserUsingId(t1, user3.get("id").getAsString())); } { @@ -487,11 +485,9 @@ public void testGetUserUsingIdReturnsUserFromTheRightTenantWhileQueryingFromAnyT JsonObject user2 = signInUpNumberUsingUserInputCode(t2, "+442071838750"); JsonObject user3 = signInUpNumberUsingUserInputCode(t3, "+442071838750"); - for (TenantIdentifier tenant : new TenantIdentifier[]{t1, t2, t3}) { - assertEquals(user1, getUserUsingId(tenant, user1.get("id").getAsString())); - assertEquals(user2, getUserUsingId(tenant, user2.get("id").getAsString())); - assertEquals(user3, getUserUsingId(tenant, user3.get("id").getAsString())); - } + assertEquals(user1, getUserUsingId(t1, user1.get("id").getAsString())); + assertEquals(user2, getUserUsingId(t1, user2.get("id").getAsString())); + assertEquals(user3, getUserUsingId(t1, user3.get("id").getAsString())); } } @@ -542,14 +538,12 @@ public void testUpdateEmail() throws Exception { JsonObject user = users[i]; TenantIdentifier userTenant = tenants[i]; - for (TenantIdentifier tenant : tenants) { - String newEmail = (generateRandomString(16) + "@example.com").toLowerCase(); - updateEmail(tenant, user.getAsJsonPrimitive("id").getAsString(), newEmail); - user.remove("email"); - user.addProperty("email", newEmail); + String newEmail = (generateRandomString(16) + "@example.com").toLowerCase(); + updateEmail(t1, user.getAsJsonPrimitive("id").getAsString(), newEmail); + user.remove("email"); + user.addProperty("email", newEmail); - assertEquals(user, signInUpEmailUsingLinkCode(userTenant, newEmail)); - } + assertEquals(user, signInUpEmailUsingLinkCode(userTenant, newEmail)); } } @@ -570,15 +564,13 @@ public void testUpdateNumber() throws Exception { JsonObject user = users[i]; TenantIdentifier userTenant = tenants[i]; - for (TenantIdentifier tenant : tenants) { - String newPhoneNumber = generateRandomNumber(8); - updatePhoneNumber(tenant, user.getAsJsonPrimitive("id").getAsString(), newPhoneNumber); - user.remove("phoneNumber"); - // We need to normalize the phone number before adding it to the user object, as the update API performs normalization. - user.addProperty("phoneNumber", io.supertokens.utils.Utils.normalizeIfPhoneNumber(newPhoneNumber)); + String newPhoneNumber = generateRandomNumber(8); + updatePhoneNumber(t1, user.getAsJsonPrimitive("id").getAsString(), newPhoneNumber); + user.remove("phoneNumber"); + // We need to normalize the phone number before adding it to the user object, as the update API performs normalization. + user.addProperty("phoneNumber", io.supertokens.utils.Utils.normalizeIfPhoneNumber(newPhoneNumber)); - assertEquals(user, signInUpNumberUsingUserInputCode(userTenant, newPhoneNumber)); - } + assertEquals(user, signInUpNumberUsingUserInputCode(userTenant, newPhoneNumber)); } } } diff --git a/src/test/java/io/supertokens/test/thirdparty/api/MultitenantAPITest.java b/src/test/java/io/supertokens/test/thirdparty/api/MultitenantAPITest.java index fbdfe54b8..53a2b9235 100644 --- a/src/test/java/io/supertokens/test/thirdparty/api/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/thirdparty/api/MultitenantAPITest.java @@ -252,7 +252,7 @@ public void testGetUserUsingIdReturnsUserFromTheRightTenantWhileQueryingFromAnyT JsonObject user2 = signInUp(t2, "google", "google-user-id", "user@gmail.com"); JsonObject user3 = signInUp(t3, "google", "google-user-id", "user@gmail.com"); - for (TenantIdentifier tenant : new TenantIdentifier[]{t1, t2, t3}) { + for (TenantIdentifier tenant : new TenantIdentifier[]{t1}) { // Only public tenant can get user by id assertEquals(user1, getUserUsingId(tenant, user1.get("id").getAsString())); assertEquals(user2, getUserUsingId(tenant, user2.get("id").getAsString())); assertEquals(user3, getUserUsingId(tenant, user3.get("id").getAsString())); diff --git a/src/test/java/io/supertokens/test/totp/TOTPRecipeTest.java b/src/test/java/io/supertokens/test/totp/TOTPRecipeTest.java index 4afc4d279..9e809d86e 100644 --- a/src/test/java/io/supertokens/test/totp/TOTPRecipeTest.java +++ b/src/test/java/io/supertokens/test/totp/TOTPRecipeTest.java @@ -604,6 +604,9 @@ public void testCurrentAndMaxAttemptsInExceptions() throws Exception { TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + FeatureFlagTestContent.getInstance(process.main) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MFA}); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { return; } diff --git a/src/test/java/io/supertokens/test/totp/api/MultitenantAPITest.java b/src/test/java/io/supertokens/test/totp/api/MultitenantAPITest.java index d90b85010..de37b2c8c 100644 --- a/src/test/java/io/supertokens/test/totp/api/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/totp/api/MultitenantAPITest.java @@ -18,6 +18,7 @@ import com.google.gson.JsonObject; import io.supertokens.ProcessState; +import io.supertokens.emailpassword.EmailPassword; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; @@ -25,6 +26,7 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.*; @@ -46,8 +48,7 @@ import java.io.IOException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; public class MultitenantAPITest { TestingProcessManager.TestingProcess process; @@ -105,7 +106,7 @@ private void createTenants() new TenantIdentifier(null, null, null), new TenantConfig( tenantIdentifier, - new EmailPasswordConfig(false), + new EmailPasswordConfig(true), new ThirdPartyConfig(false, null), new PasswordlessConfig(true), null, null, @@ -126,7 +127,7 @@ private void createTenants() new TenantIdentifier(null, "a1", null), new TenantConfig( tenantIdentifier, - new EmailPasswordConfig(false), + new EmailPasswordConfig(true), new ThirdPartyConfig(false, null), new PasswordlessConfig(true), null, null, @@ -147,7 +148,7 @@ private void createTenants() new TenantIdentifier(null, "a1", null), new TenantConfig( tenantIdentifier, - new EmailPasswordConfig(false), + new EmailPasswordConfig(true), new ThirdPartyConfig(false, null), new PasswordlessConfig(true), null, null, @@ -244,24 +245,27 @@ private void validateTotp(TenantIdentifier tenantIdentifier, String userId, Stri } @Test - public void testDevicesWorkAppWide() throws Exception { + public void testCreateDeviceWorksFromPublicTenantOnly() throws Exception { if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { return; } TenantIdentifier[] tenants = new TenantIdentifier[]{t1, t2, t3}; int userCount = 1; - for (TenantIdentifier tenant1 : tenants) { - createDevice(tenant1, "user" + userCount); - TOTPDevice device = Totp.getDevices(t1.withStorage(StorageLayer.getStorage(tenant1, process.getProcess())).toAppIdentifierWithStorage(), "user" + userCount)[0]; - String validTotp = TOTPRecipeTest.generateTotpCode(process.getProcess(), device); - verifyDevice(tenant1, "user" + userCount, validTotp); - for (TenantIdentifier tenant2 : tenants) { - createDeviceAlreadyExists(tenant2, "user" + userCount); - } + createDevice(t1, "user" + userCount); + TOTPDevice device = Totp.getDevices(t1.toAppIdentifier(), (StorageLayer.getStorage(t1, process.getProcess())), + "user" + userCount)[0]; + String validTotp = TOTPRecipeTest.generateTotpCode(process.getProcess(), device); + verifyDevice(t1, "user" + userCount, validTotp); - userCount++; + userCount++; + + try { + createDevice(t2, "user" + userCount); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); } } @@ -274,16 +278,21 @@ public void testSameCodeUsedOnDifferentTenantsIsAllowed() throws Exception { TenantIdentifier[] tenants = new TenantIdentifier[]{t2, t3}; int userCount = 1; for (TenantIdentifier tenant1 : tenants) { - JsonObject deviceResponse = createDevice(tenant1, "user" + userCount); + AuthRecipeUserInfo user = EmailPassword.signUp( + tenant1, (StorageLayer.getStorage(tenant1, process.getProcess())), process.getProcess(), + "test@example.com", "password1"); + String userId = user.getSupertokensUserId(); + + JsonObject deviceResponse = createDevice(t1, userId); String secretKey = deviceResponse.get("secret").getAsString(); TOTPDevice device = new TOTPDevice("user" + userCount, "d1", secretKey, 2, 1, true, System.currentTimeMillis()); String validTotp = TOTPRecipeTest.generateTotpCode(process.getProcess(), device); - verifyDevice(tenant1, "user" + userCount, validTotp); + verifyDevice(tenant1, userId, validTotp); Thread.sleep(2500); // Wait for a new TOTP String validTotp2 = TOTPRecipeTest.generateTotpCode(process.getProcess(), device); for (TenantIdentifier tenant2 : tenants) { - validateTotp(tenant2, "user" + userCount, validTotp2); + validateTotp(tenant2, userId, validTotp2); } userCount++; diff --git a/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java b/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java index f9e9b6dae..cdb7d23ae 100644 --- a/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java @@ -77,7 +77,7 @@ public void testExternalUserIdTranslation() throws Exception { "totp"); assert res1.get("status").getAsString().equals("OK"); String d1Secret = res1.get("secret").getAsString(); - TOTPDevice device1 = new TOTPDevice(externalUserId, "deviceName", d1Secret, 30, 1, false, System.currentTimeMillis()); + TOTPDevice device1 = new TOTPDevice(externalUserId, "d1", d1Secret, 30, 0, false, System.currentTimeMillis()); body.addProperty("deviceName", "d2"); @@ -93,7 +93,7 @@ public void testExternalUserIdTranslation() throws Exception { "totp"); assert res2.get("status").getAsString().equals("OK"); String d2Secret = res2.get("secret").getAsString(); - TOTPDevice device2 = new TOTPDevice(externalUserId, "deviceName", d2Secret, 30, 1, false, System.currentTimeMillis()); + TOTPDevice device2 = new TOTPDevice(externalUserId, "d2", d2Secret, 30, 0, false, System.currentTimeMillis()); // Verify d1 but not d2: JsonObject verifyD1Input = new JsonObject(); diff --git a/src/test/java/io/supertokens/test/userIdMapping/api/MultitenantAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/api/MultitenantAPITest.java index d36dfc1cb..bb975b7db 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/api/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/api/MultitenantAPITest.java @@ -271,12 +271,12 @@ public void testUserIdMappingWorksCorrectlyAcrossTenants() throws Exception { user3.addProperty("externalUserId", "euserid3"); - for (TenantIdentifier createTenant: new TenantIdentifier[]{t1, t2, t3}) { + for (TenantIdentifier createTenant: new TenantIdentifier[]{t1}) { successfulCreateUserIdMapping(createTenant, user1.get("id").getAsString(), "euserid1"); successfulCreateUserIdMapping(createTenant, user2.get("id").getAsString(), "euserid2"); successfulCreateUserIdMapping(createTenant, user3.get("id").getAsString(), "euserid3"); - for (TenantIdentifier queryTenant : new TenantIdentifier[]{t1, t2, t3}) { + for (TenantIdentifier queryTenant : new TenantIdentifier[]{t1}) { for (JsonObject user : new JsonObject[]{user1, user2, user3}) { { JsonObject mapping = getUserIdMapping(queryTenant, user.get("id").getAsString(), "SUPERTOKENS"); @@ -328,8 +328,8 @@ public void testSameExternalIdIsDisallowedIrrespectiveOfUserPool() throws Except String externalUserId = "euserid" + (testcase++); - successfulCreateUserIdMapping(tx, user1.get("id").getAsString(), externalUserId); - mappingAlreadyExistsWithCreateUserIdMapping(ty, user2.get("id").getAsString(), externalUserId); + successfulCreateUserIdMapping(t1, user1.get("id").getAsString(), externalUserId); + mappingAlreadyExistsWithCreateUserIdMapping(t1, user2.get("id").getAsString(), externalUserId); } } } @@ -348,39 +348,35 @@ public void testRemoveMappingWorksAppWide() throws Exception { String externalUserId = "euserid" + userCount; user.addProperty("externalUserId", externalUserId); - for (TenantIdentifier tx : tenants) { - for (TenantIdentifier ty : tenants) { - { - successfulCreateUserIdMapping(tx, user.get("id").getAsString(), externalUserId); - getUserIdMapping(ty, user.get("id").getAsString(), "SUPERTOKENS"); - successfulRemoveUserIdMapping(ty, user.get("id").getAsString(), "SUPERTOKENS"); - getUnknownUserIdMapping(ty, user.get("id").getAsString(), "SUPERTOKENS"); - } - { - successfulCreateUserIdMapping(tx, user.get("id").getAsString(), externalUserId); - getUserIdMapping(ty, user.get("id").getAsString(), "ANY"); - successfulRemoveUserIdMapping(ty, user.get("id").getAsString(), "ANY"); - getUnknownUserIdMapping(ty, user.get("id").getAsString(), "ANY"); - } - { - successfulCreateUserIdMapping(tx, user.get("id").getAsString(), externalUserId); - getUserIdMapping(ty, user.get("externalUserId").getAsString(), "EXTERNAL"); - successfulRemoveUserIdMapping(ty, user.get("externalUserId").getAsString(), "EXTERNAL"); - getUnknownUserIdMapping(ty, user.get("externalUserId").getAsString(), "EXTERNAL"); - } - { - successfulCreateUserIdMapping(tx, user.get("id").getAsString(), externalUserId); - getUserIdMapping(ty, user.get("externalUserId").getAsString(), "ANY"); - successfulRemoveUserIdMapping(ty, user.get("externalUserId").getAsString(), "ANY"); - getUnknownUserIdMapping(ty, user.get("externalUserId").getAsString(), "ANY"); - } - } + { + successfulCreateUserIdMapping(t1, user.get("id").getAsString(), externalUserId); + getUserIdMapping(t1, user.get("id").getAsString(), "SUPERTOKENS"); + successfulRemoveUserIdMapping(t1, user.get("id").getAsString(), "SUPERTOKENS"); + getUnknownUserIdMapping(t1, user.get("id").getAsString(), "SUPERTOKENS"); + } + { + successfulCreateUserIdMapping(t1, user.get("id").getAsString(), externalUserId); + getUserIdMapping(t1, user.get("id").getAsString(), "ANY"); + successfulRemoveUserIdMapping(t1, user.get("id").getAsString(), "ANY"); + getUnknownUserIdMapping(t1, user.get("id").getAsString(), "ANY"); + } + { + successfulCreateUserIdMapping(t1, user.get("id").getAsString(), externalUserId); + getUserIdMapping(t1, user.get("externalUserId").getAsString(), "EXTERNAL"); + successfulRemoveUserIdMapping(t1, user.get("externalUserId").getAsString(), "EXTERNAL"); + getUnknownUserIdMapping(t1, user.get("externalUserId").getAsString(), "EXTERNAL"); + } + { + successfulCreateUserIdMapping(t1, user.get("id").getAsString(), externalUserId); + getUserIdMapping(t1, user.get("externalUserId").getAsString(), "ANY"); + successfulRemoveUserIdMapping(t1, user.get("externalUserId").getAsString(), "ANY"); + getUnknownUserIdMapping(t1, user.get("externalUserId").getAsString(), "ANY"); } } } @Test - public void testSameExternalIdAcrossUserPoolPrioritizesTenantOfInterest() throws Exception { + public void testSameExternalIdAcrossUserPoolJustReturnsOneOfThem() throws Exception { if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { return; } @@ -400,29 +396,30 @@ public void testSameExternalIdAcrossUserPoolPrioritizesTenantOfInterest() throws { JsonObject mapping = getUserIdMapping(t1, "euserid", "EXTERNAL"); - assertEquals(user1.get("id").getAsString(), mapping.get("superTokensUserId").getAsString()); + assert mapping.get("superTokensUserId").getAsString().equals(user1.get("id").getAsString()) + || mapping.get("superTokensUserId").getAsString().equals(user2.get("id").getAsString()); } { JsonObject mapping = getUserIdMapping(t1, "euserid", "ANY"); - assertEquals(user1.get("id").getAsString(), mapping.get("superTokensUserId").getAsString()); + assert mapping.get("superTokensUserId").getAsString().equals(user1.get("id").getAsString()) + || mapping.get("superTokensUserId").getAsString().equals(user2.get("id").getAsString()); } { - JsonObject mapping = getUserIdMapping(t2, "euserid", "EXTERNAL"); - assertEquals(user2.get("id").getAsString(), mapping.get("superTokensUserId").getAsString()); - } - { - JsonObject mapping = getUserIdMapping(t2, "euserid", "ANY"); - assertEquals(user2.get("id").getAsString(), mapping.get("superTokensUserId").getAsString()); - } - - { - JsonObject mapping = getUserIdMapping(t3, "euserid", "EXTERNAL"); - assertEquals(user2.get("id").getAsString(), mapping.get("superTokensUserId").getAsString()); + try { + JsonObject mapping = getUserIdMapping(t2, "euserid", "EXTERNAL"); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } } { - JsonObject mapping = getUserIdMapping(t3, "euserid", "ANY"); - assertEquals(user2.get("id").getAsString(), mapping.get("superTokensUserId").getAsString()); + try { + JsonObject mapping = getUserIdMapping(t2, "euserid", "ANY"); + fail(); + } catch (HttpResponseException e) { + assertEquals(403, e.statusCode); + } } } diff --git a/src/test/java/io/supertokens/test/userRoles/UserRolesStorageTest.java b/src/test/java/io/supertokens/test/userRoles/UserRolesStorageTest.java index cefb5a08c..5c8437e0b 100644 --- a/src/test/java/io/supertokens/test/userRoles/UserRolesStorageTest.java +++ b/src/test/java/io/supertokens/test/userRoles/UserRolesStorageTest.java @@ -18,6 +18,7 @@ import io.supertokens.ProcessState; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -225,7 +226,8 @@ public void testCreatingAndAddingAPermissionToARoleInTransactionAndDeletingRole( } // delete the newly created role try { - storage.deleteRole(new AppIdentifier(null, null), role); + boolean wasRoleDeleted = storage.deleteAllUserRoleAssociationsForRole(new AppIdentifier(null, null), role); + wasRoleDeleted = storage.deleteRole(new AppIdentifier(null, null), role) || wasRoleDeleted; r2_success.set(true); } catch (StorageQueryException e) { // should not come here @@ -470,8 +472,9 @@ public void testAssociatingAnUnknownRoleWithUser() throws Exception { Exception error = null; try { - - storage.addRoleToUser(new TenantIdentifier(null, null, null), "userId", "unknownRole"); + UserRoles.addRoleToUser( + process.getProcess(), new TenantIdentifier(null, null, null), + StorageLayer.getBaseStorage(process.getProcess()), "userId", "unknownRole"); } catch (Exception e) { error = e; } @@ -825,7 +828,8 @@ public void testDeletingRoleResponses() throws Exception { UserRoles.createNewRoleOrModifyItsPermissions(process.main, role, null); // delete role - boolean didRoleExist = storage.deleteRole(new AppIdentifier(null, null), role); + boolean didRoleExist = storage.deleteAllUserRoleAssociationsForRole(new AppIdentifier(null, null), role); + assertTrue(didRoleExist = storage.deleteRole(new AppIdentifier(null, null), role) || didRoleExist); assertTrue(didRoleExist); // check that role doesnt exist @@ -833,8 +837,8 @@ public void testDeletingRoleResponses() throws Exception { } { // delete a role which doesnt exist - - boolean didRoleExist = storage.deleteRole(new AppIdentifier(null, null), role); + boolean didRoleExist = storage.deleteAllUserRoleAssociationsForRole(new AppIdentifier(null, null), role); + didRoleExist = storage.deleteRole(new AppIdentifier(null, null), role) || didRoleExist; assertFalse(didRoleExist); } From 3f46b5d232ea8c57edf5e0c301f3e9c4c62ed4bc Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 11 Mar 2024 11:54:30 +0530 Subject: [PATCH 21/23] fix: add check code API and update delete code API (#948) * fix: verify code API * pr comments * fix: cleanup * fix: PR comments * fix: pr comment --- .../passwordless/Passwordless.java | 141 +++++++----- .../io/supertokens/webserver/Webserver.java | 1 + .../api/passwordless/CheckCodeAPI.java | 118 ++++++++++ .../api/passwordless/ConsumeCodeAPI.java | 66 ++---- .../api/passwordless/DeleteCodeAPI.java | 24 +- ...a => PasswordlessCheckCodeAPITest5_0.java} | 197 +++++++--------- .../api/PasswordlessDeleteCodeAPITest5_0.java | 215 ++++++++++++++++++ 7 files changed, 546 insertions(+), 216 deletions(-) create mode 100644 src/main/java/io/supertokens/webserver/api/passwordless/CheckCodeAPI.java rename src/test/java/io/supertokens/test/passwordless/api/{PasswordlessConsumeCodeAPITest5_0.java => PasswordlessCheckCodeAPITest5_0.java} (89%) create mode 100644 src/test/java/io/supertokens/test/passwordless/api/PasswordlessDeleteCodeAPITest5_0.java 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)); + } +} From 233ce9ec0425947f130e28b7d10d1eaf206bca63 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Mon, 11 Mar 2024 13:18:10 +0530 Subject: [PATCH 22/23] fix: build --- .../io/supertokens/bulkimport/BulkImport.java | 25 ++++++++------ .../api/bulkimport/BulkImportAPI.java | 34 ++++++++++++------- .../test/bulkimport/BulkImportTest.java | 31 ++++++++--------- .../apis/DeleteBulkImportUsersTest.java | 6 ++-- 4 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index 329fc7e63..1fe2ba2d4 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -17,9 +17,12 @@ package io.supertokens.bulkimport; import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; +import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.Utils; @@ -36,11 +39,11 @@ public class BulkImport { public static final int GET_USERS_DEFAULT_LIMIT = 100; public static final int DELETE_USERS_LIMIT = 500; - public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, List users) + public static void addUsers(AppIdentifier appIdentifier, Storage storage, List users) throws StorageQueryException, TenantOrAppNotFoundException { while (true) { try { - appIdentifierWithStorage.getBulkImportStorage().addBulkImportUsers(appIdentifierWithStorage, users); + StorageUtils.getBulkImportStorage(storage).addBulkImportUsers(appIdentifier, users); break; } catch (io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException ignored) { // We re-generate the user id for every user and retry @@ -51,18 +54,20 @@ public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, L } } - public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorage appIdentifierWithStorage, + public static BulkImportUserPaginationContainer getUsers(AppIdentifier appIdentifier, Storage storage, @Nonnull Integer limit, @Nullable BULK_IMPORT_USER_STATUS status, @Nullable String paginationToken) throws StorageQueryException, BulkImportUserPaginationToken.InvalidTokenException { List users; + BulkImportSQLStorage bulkImportStorage = StorageUtils.getBulkImportStorage(storage); + if (paginationToken == null) { - users = appIdentifierWithStorage.getBulkImportStorage() - .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, null, null); + users = bulkImportStorage + .getBulkImportUsers(appIdentifier, limit + 1, status, null, null); } else { BulkImportUserPaginationToken tokenInfo = BulkImportUserPaginationToken.extractTokenInfo(paginationToken); - users = appIdentifierWithStorage.getBulkImportStorage() - .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, tokenInfo.bulkImportUserId, tokenInfo.createdAt); + users = bulkImportStorage + .getBulkImportUsers(appIdentifier, limit + 1, status, tokenInfo.bulkImportUserId, tokenInfo.createdAt); } String nextPaginationToken = null; @@ -77,7 +82,7 @@ public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorag return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken); } - public static List deleteUsers(AppIdentifierWithStorage appIdentifierWithStorage, String[] userIds) throws StorageQueryException { - return appIdentifierWithStorage.getBulkImportStorage().deleteBulkImportUsers(appIdentifierWithStorage, userIds); + public static List deleteUsers(AppIdentifier appIdentifier, Storage storage, String[] userIds) throws StorageQueryException { + return StorageUtils.getBulkImportStorage(storage).deleteBulkImportUsers(appIdentifier, userIds); } } diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java index af2a29c9c..590c7b563 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java @@ -32,9 +32,11 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; @@ -79,16 +81,18 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se } } - AppIdentifierWithStorage appIdentifierWithStorage = null; + AppIdentifier appIdentifier = null; + Storage storage = null; try { - appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + appIdentifier = getAppIdentifier(req); + storage = enforcePublicTenantAndGetPublicTenantStorage(req); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } try { - BulkImportUserPaginationContainer users = BulkImport.getUsers(appIdentifierWithStorage, limit, status, paginationToken); + BulkImportUserPaginationContainer users = BulkImport.getUsers(appIdentifier, storage, limit, status, paginationToken); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -122,10 +126,13 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S errorResponseJson.addProperty("error", errorMsg); throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); } + + AppIdentifier appIdentifier = null; + Storage storage = null; - AppIdentifierWithStorage appIdentifierWithStorage; try { - appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + appIdentifier = getAppIdentifier(req); + storage = enforcePublicTenantAndGetPublicTenantStorage(req); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } @@ -133,7 +140,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S String[] allUserRoles = null; try { - allUserRoles = appIdentifierWithStorage.getUserRolesStorage().getRoles(appIdentifierWithStorage); + allUserRoles = StorageUtils.getUserRolesStorage(storage).getRoles(appIdentifier); } catch (StorageQueryException e) { throw new ServletException(e); } @@ -143,7 +150,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S for (int i = 0; i < users.size(); i++) { try { - BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifierWithStorage, users.get(i).getAsJsonObject(), Utils.getUUID(), allUserRoles); + BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifier, users.get(i).getAsJsonObject(), Utils.getUUID(), allUserRoles); usersToAdd.add(user); } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { JsonObject errorObj = new JsonObject(); @@ -169,7 +176,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S } try { - BulkImport.addUsers(appIdentifierWithStorage, usersToAdd); + BulkImport.addUsers(appIdentifier, storage, usersToAdd); } catch (TenantOrAppNotFoundException | StorageQueryException e) { throw new ServletException(e); } @@ -203,15 +210,18 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws userIds[i] = userId; } - AppIdentifierWithStorage appIdentifierWithStorage; + AppIdentifier appIdentifier = null; + Storage storage = null; + try { - appIdentifierWithStorage = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req); + appIdentifier = getAppIdentifier(req); + storage = enforcePublicTenantAndGetPublicTenantStorage(req); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } try { - List deletedIds = BulkImport.deleteUsers(appIdentifierWithStorage, userIds); + List deletedIds = BulkImport.deleteUsers(appIdentifier, storage, userIds); JsonArray deletedIdsJson = new JsonArray(); JsonArray invalidIds = new JsonArray(); diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java index dba270c3b..07dc835ad 100644 --- a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java @@ -38,7 +38,6 @@ import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -73,7 +72,7 @@ public void shouldAddUsersInBulkImportUsersTable() throws Exception { List users = generateBulkImportUser(10); BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); - BulkImport.addUsers(new AppIdentifierWithStorage(null, null, storage), users); + BulkImport.addUsers(new AppIdentifier(null, null), storage, users); List addedUsers = storage.getBulkImportUsers(new AppIdentifier(null, null), null, BULK_IMPORT_USER_STATUS.NEW, null, null); @@ -112,10 +111,10 @@ public void shouldCreatedNewIdsIfDuplicateIdIsFound() throws Exception { List initialIds = users.stream().map(user -> user.id).collect(Collectors.toList()); BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); - AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); - BulkImport.addUsers(appIdentifierWithStorage, users); + AppIdentifier appIdentifier = new AppIdentifier(null, null); + BulkImport.addUsers(appIdentifier, storage, users); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BULK_IMPORT_USER_STATUS.NEW, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifier, null, BULK_IMPORT_USER_STATUS.NEW, null, null); // Verify that the other properties are same but ids changed for (BulkImportUser user : users) { @@ -145,50 +144,50 @@ public void testGetUsersStatusFilter() throws Exception { } BulkImportSQLStorage storage = (BulkImportSQLStorage) StorageLayer.getStorage(process.main); - AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); + AppIdentifier appIdentifier = new AppIdentifier(null, null); // Test with status = 'NEW' { List users = generateBulkImportUser(10); - BulkImport.addUsers(appIdentifierWithStorage, users); + BulkImport.addUsers(appIdentifier, storage, users); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BULK_IMPORT_USER_STATUS.NEW, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifier, null, BULK_IMPORT_USER_STATUS.NEW, null, null); assertEquals(10, addedUsers.size()); } // Test with status = 'PROCESSING' { List users = generateBulkImportUser(10); - BulkImport.addUsers(appIdentifierWithStorage, users); + BulkImport.addUsers(appIdentifier, storage, users); // Update the users status to PROCESSING String[] userIds = users.stream().map(user -> user.id).toArray(String[]::new); storage.startTransaction(con -> { - storage.updateBulkImportUserStatus_Transaction(appIdentifierWithStorage, con, userIds, BULK_IMPORT_USER_STATUS.PROCESSING); + storage.updateBulkImportUserStatus_Transaction(appIdentifier, con, userIds, BULK_IMPORT_USER_STATUS.PROCESSING); storage.commitTransaction(con); return null; }); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BULK_IMPORT_USER_STATUS.PROCESSING, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifier, null, BULK_IMPORT_USER_STATUS.PROCESSING, null, null); assertEquals(10, addedUsers.size()); } // Test with status = 'FAILED' { List users = generateBulkImportUser(10); - BulkImport.addUsers(appIdentifierWithStorage, users); + BulkImport.addUsers(appIdentifier, storage, users); // Update the users status to FAILED String[] userIds = users.stream().map(user -> user.id).toArray(String[]::new); storage.startTransaction(con -> { - storage.updateBulkImportUserStatus_Transaction(appIdentifierWithStorage, con, userIds, BULK_IMPORT_USER_STATUS.FAILED); + storage.updateBulkImportUserStatus_Transaction(appIdentifier, con, userIds, BULK_IMPORT_USER_STATUS.FAILED); storage.commitTransaction(con); return null; }); - List addedUsers = storage.getBulkImportUsers(appIdentifierWithStorage, null, BULK_IMPORT_USER_STATUS.FAILED, null, null); + List addedUsers = storage.getBulkImportUsers(appIdentifier, null, BULK_IMPORT_USER_STATUS.FAILED, null, null); assertEquals(10, addedUsers.size()); } @@ -215,7 +214,7 @@ public void randomPaginationTest() throws Exception { int batchSize = 100; for (int i = 0; i < numberOfUsers; i += batchSize) { List users = generateBulkImportUser(batchSize); - BulkImport.addUsers(new AppIdentifierWithStorage(null, null, storage), users); + BulkImport.addUsers(new AppIdentifier(null, null), storage, users); // Adding a delay between each batch to ensure the createdAt different Thread.sleep(1000); } @@ -242,7 +241,7 @@ public void randomPaginationTest() throws Exception { int indexIntoUsers = 0; String paginationToken = null; do { - BulkImportUserPaginationContainer users = BulkImport.getUsers(new AppIdentifierWithStorage(null, null, storage), limit, null, paginationToken); + BulkImportUserPaginationContainer users = BulkImport.getUsers(new AppIdentifier(null, null), storage, limit, null, paginationToken); for (BulkImportUser actualUser : users.users) { BulkImportUser expectedUser = sortedUsers.get(indexIntoUsers); diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java index f4edc4ba3..c3499f0a1 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java @@ -39,7 +39,7 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -139,12 +139,12 @@ public void shouldReturn200Response() throws Exception { return; } + AppIdentifier appIdentifier = new AppIdentifier(null, null); BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); - AppIdentifierWithStorage appIdentifierWithStorage = new AppIdentifierWithStorage(null, null, storage); // Insert users List users = generateBulkImportUser(5); - BulkImport.addUsers(appIdentifierWithStorage, users); + BulkImport.addUsers(appIdentifier, storage, users); String invalidId = io.supertokens.utils.Utils.getUUID(); JsonObject request = new JsonObject(); From ba4e11bb927b5ac48747a9f8a736c0f425fc3d70 Mon Sep 17 00:00:00 2001 From: Ankit Tiwari Date: Wed, 20 Mar 2024 11:31:55 +0530 Subject: [PATCH 23/23] feat: Add ProcessBulkImportUsers cron job --- src/main/java/io/supertokens/Main.java | 4 + .../io/supertokens/bulkimport/BulkImport.java | 2 + .../bulkimport/BulkImportUserUtils.java | 240 +++++++-- .../bulkimport/ProcessBulkImportUsers.java | 504 ++++++++++++++++++ .../emailpassword/EmailPassword.java | 70 ++- .../java/io/supertokens/inmemorydb/Start.java | 5 + .../passwordless/Passwordless.java | 87 +-- .../storageLayer/StorageLayer.java | 14 +- .../io/supertokens/thirdparty/ThirdParty.java | 39 +- .../api/bulkimport/BulkImportAPI.java | 15 +- .../test/bulkimport/BulkImportTest.java | 4 +- .../test/bulkimport/BulkImportTestUtils.java | 12 +- .../ProcessBulkImportUsersCronJobTest.java | 297 +++++++++++ .../apis/AddBulkImportUsersTest.java | 191 ++++++- .../apis/DeleteBulkImportUsersTest.java | 2 +- .../apis/GetBulkImportUsersTest.java | 11 +- 16 files changed, 1310 insertions(+), 187 deletions(-) create mode 100644 src/main/java/io/supertokens/cronjobs/bulkimport/ProcessBulkImportUsers.java create mode 100644 src/test/java/io/supertokens/test/bulkimport/ProcessBulkImportUsersCronJobTest.java diff --git a/src/main/java/io/supertokens/Main.java b/src/main/java/io/supertokens/Main.java index 2998efb7b..7375e6e08 100644 --- a/src/main/java/io/supertokens/Main.java +++ b/src/main/java/io/supertokens/Main.java @@ -20,6 +20,7 @@ import io.supertokens.config.Config; import io.supertokens.config.CoreConfig; import io.supertokens.cronjobs.Cronjobs; +import io.supertokens.cronjobs.bulkimport.ProcessBulkImportUsers; import io.supertokens.cronjobs.deleteExpiredAccessTokenSigningKeys.DeleteExpiredAccessTokenSigningKeys; import io.supertokens.cronjobs.deleteExpiredDashboardSessions.DeleteExpiredDashboardSessions; import io.supertokens.cronjobs.deleteExpiredEmailVerificationTokens.DeleteExpiredEmailVerificationTokens; @@ -254,6 +255,9 @@ private void init() throws IOException, StorageQueryException { // starts DeleteExpiredAccessTokenSigningKeys cronjob if the access token signing keys can change Cronjobs.addCronjob(this, DeleteExpiredAccessTokenSigningKeys.init(this, uniqueUserPoolIdsTenants)); + // starts ProcessBulkImportUsers cronjob to process bulk import users + Cronjobs.addCronjob(this, ProcessBulkImportUsers.init(this, uniqueUserPoolIdsTenants)); + // this is to ensure tenantInfos are in sync for the new cron job as well MultitenancyHelper.getInstance(this).refreshCronjobs(); diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java index 1fe2ba2d4..8bfabc7e1 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImport.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -38,6 +38,8 @@ public class BulkImport { public static final int GET_USERS_PAGINATION_LIMIT = 500; public static final int GET_USERS_DEFAULT_LIMIT = 100; public static final int DELETE_USERS_LIMIT = 500; + public static final int PROCESS_USERS_BATCH_SIZE = 1000; + public static final int PROCESS_USERS_INTERVAL = 60; public static void addUsers(AppIdentifier appIdentifier, Storage storage, List users) throws StorageQueryException, TenantOrAppNotFoundException { diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java index c006c3640..c96a10fa6 100644 --- a/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -34,14 +34,17 @@ import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlag; import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.UserRole; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantConfig; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.Utils; import io.supertokens.utils.JsonValidatorUtils.ValueType; @@ -49,19 +52,23 @@ import static io.supertokens.utils.JsonValidatorUtils.validateJsonFieldType; public class BulkImportUserUtils { - public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifier appIdentifier, JsonObject userData, String id, String[] allUserRoles) + public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifier appIdentifier, + JsonObject userData, String id, String[] allUserRoles, Set allExternalUserIds) throws InvalidBulkImportDataException, StorageQueryException, TenantOrAppNotFoundException { List errors = new ArrayList<>(); - String externalUserId = parseAndValidateFieldType(userData, "externalUserId", ValueType.STRING, false, String.class, + String externalUserId = parseAndValidateFieldType(userData, "externalUserId", ValueType.STRING, false, + String.class, errors, "."); JsonObject userMetadata = parseAndValidateFieldType(userData, "userMetadata", ValueType.OBJECT, false, JsonObject.class, errors, "."); - List userRoles = getParsedUserRoles(userData, allUserRoles, errors); + List userRoles = getParsedUserRoles(main, appIdentifier, userData, allUserRoles, errors); List totpDevices = getParsedTotpDevices(userData, errors); List loginMethods = getParsedLoginMethods(main, appIdentifier, userData, errors); - externalUserId = validateAndNormaliseExternalUserId(externalUserId, errors); + externalUserId = validateAndNormaliseExternalUserId(externalUserId, allExternalUserIds, errors); + + validateTenantIdsForRoleAndLoginMethods(main, appIdentifier, userRoles, loginMethods, errors); if (!errors.isEmpty()) { throw new InvalidBulkImportDataException(errors); @@ -69,23 +76,40 @@ public static BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifi return new BulkImportUser(id, externalUserId, userMetadata, userRoles, totpDevices, loginMethods); } - private static List getParsedUserRoles(JsonObject userData, String[] allUserRoles, List errors) { - JsonArray jsonUserRoles = parseAndValidateFieldType(userData, "userRoles", ValueType.ARRAY_OF_STRING, - false, + private static List getParsedUserRoles(Main main, AppIdentifier appIdentifier, JsonObject userData, + String[] allUserRoles, List errors) throws StorageQueryException, TenantOrAppNotFoundException { + JsonArray jsonUserRoles = parseAndValidateFieldType(userData, "userRoles", ValueType.ARRAY_OF_OBJECT, false, JsonArray.class, errors, "."); if (jsonUserRoles == null) { return null; } - // We already know that the jsonUserRoles is an array of non-empty strings, we will normalise each role now - List userRoles = new ArrayList<>(); - jsonUserRoles.forEach(role -> userRoles.add(validateAndNormaliseUserRole(role.getAsString(), allUserRoles, errors))); + List userRoles = new ArrayList<>(); + + for (JsonElement jsonUserRoleEl : jsonUserRoles) { + JsonObject jsonUserRole = jsonUserRoleEl.getAsJsonObject(); + + String role = parseAndValidateFieldType(jsonUserRole, "role", ValueType.STRING, true, String.class, errors, + " for a user role."); + JsonArray jsonTenantIds = parseAndValidateFieldType(jsonUserRole, "tenantIds", ValueType.ARRAY_OF_STRING, + true, JsonArray.class, errors, " for a user role."); + + role = validateAndNormaliseUserRole(role, allUserRoles, errors); + List normalisedTenantIds = validateAndNormaliseTenantIds(main, appIdentifier, jsonTenantIds, errors, + " for a user role."); + + if (role != null && normalisedTenantIds != null) { + userRoles.add(new UserRole(role, normalisedTenantIds)); + } + } return userRoles; } private static List getParsedTotpDevices(JsonObject userData, List errors) { - JsonArray jsonTotpDevices = parseAndValidateFieldType(userData, "totpDevices", ValueType.ARRAY_OF_OBJECT, false, JsonArray.class, errors, "."); + JsonArray jsonTotpDevices = parseAndValidateFieldType(userData, "totpDevices", ValueType.ARRAY_OF_OBJECT, false, + JsonArray.class, errors, "."); + if (jsonTotpDevices == null) { return null; } @@ -94,10 +118,14 @@ private static List getParsedTotpDevices(JsonObject userData, List getParsedTotpDevices(JsonObject userData, List getParsedLoginMethods(Main main, AppIdentifier appIdentifier, JsonObject userData, List errors) + private static List getParsedLoginMethods(Main main, AppIdentifier appIdentifier, JsonObject userData, + List errors) throws StorageQueryException, TenantOrAppNotFoundException { - JsonArray jsonLoginMethods = parseAndValidateFieldType(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, true, JsonArray.class, errors, "."); + JsonArray jsonLoginMethods = parseAndValidateFieldType(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT, + true, JsonArray.class, errors, "."); if (jsonLoginMethods == null) { return new ArrayList<>(); @@ -131,55 +161,80 @@ private static List getParsedLoginMethods(Main main, AppIdentifier for (JsonElement jsonLoginMethod : jsonLoginMethods) { JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject(); - String recipeId = parseAndValidateFieldType(jsonLoginMethodObj, "recipeId", ValueType.STRING, true, String.class, errors, " for a loginMethod."); - String tenantId = parseAndValidateFieldType(jsonLoginMethodObj, "tenantId", ValueType.STRING, false, String.class, errors, " for a loginMethod."); - Boolean isVerified = parseAndValidateFieldType(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); - Boolean isPrimary = parseAndValidateFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, Boolean.class, errors, " for a loginMethod."); - Long timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.LONG, false, Long.class, errors, " for a loginMethod"); + String recipeId = parseAndValidateFieldType(jsonLoginMethodObj, "recipeId", ValueType.STRING, true, + String.class, errors, " for a loginMethod."); + JsonArray tenantIds = parseAndValidateFieldType(jsonLoginMethodObj, "tenantIds", ValueType.ARRAY_OF_STRING, + false, JsonArray.class, errors, " for a loginMethod."); + Boolean isVerified = parseAndValidateFieldType(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false, + Boolean.class, errors, " for a loginMethod."); + Boolean isPrimary = parseAndValidateFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false, + Boolean.class, errors, " for a loginMethod."); + Long timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.LONG, + false, Long.class, errors, " for a loginMethod"); recipeId = validateAndNormaliseRecipeId(recipeId, errors); - tenantId= validateAndNormaliseTenantId(main, appIdentifier, tenantId, recipeId, errors); + List normalisedTenantIds = validateAndNormaliseTenantIds(main, appIdentifier, tenantIds, errors, + " for " + recipeId + " recipe."); isPrimary = validateAndNormaliseIsPrimary(isPrimary); isVerified = validateAndNormaliseIsVerified(isVerified); long timeJoinedInMSSinceEpoch = validateAndNormaliseTimeJoined(timeJoined, errors); if ("emailpassword".equals(recipeId)) { - String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); - String passwordHash = parseAndValidateFieldType(jsonLoginMethodObj, "passwordHash", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); - String hashingAlgorithm = parseAndValidateFieldType(jsonLoginMethodObj, "hashingAlgorithm", ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); + String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true, + String.class, errors, " for an emailpassword recipe."); + String passwordHash = parseAndValidateFieldType(jsonLoginMethodObj, "passwordHash", ValueType.STRING, + true, String.class, errors, " for an emailpassword recipe."); + String hashingAlgorithm = parseAndValidateFieldType(jsonLoginMethodObj, "hashingAlgorithm", + ValueType.STRING, true, String.class, errors, " for an emailpassword recipe."); email = validateAndNormaliseEmail(email, errors); - CoreConfig.PASSWORD_HASHING_ALG normalisedHashingAlgorithm = validateAndNormaliseHashingAlgorithm(hashingAlgorithm, errors); - hashingAlgorithm = normalisedHashingAlgorithm != null ? normalisedHashingAlgorithm.toString() : hashingAlgorithm; - passwordHash = validateAndNormalisePasswordHash(main, appIdentifier, normalisedHashingAlgorithm, passwordHash, errors); - - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, email, passwordHash, hashingAlgorithm, null, null, null)); + CoreConfig.PASSWORD_HASHING_ALG normalisedHashingAlgorithm = validateAndNormaliseHashingAlgorithm( + hashingAlgorithm, errors); + hashingAlgorithm = normalisedHashingAlgorithm != null ? normalisedHashingAlgorithm.toString() + : hashingAlgorithm; + passwordHash = validateAndNormalisePasswordHash(main, appIdentifier, normalisedHashingAlgorithm, + passwordHash, errors); + + loginMethods.add(new LoginMethod(normalisedTenantIds, recipeId, isVerified, isPrimary, + timeJoinedInMSSinceEpoch, email, passwordHash, hashingAlgorithm, null, null, null)); } else if ("thirdparty".equals(recipeId)) { - String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); - String thirdPartyId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); - String thirdPartyUserId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyUserId", ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); + String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true, + String.class, errors, " for a thirdparty recipe."); + String thirdPartyId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING, + true, String.class, errors, " for a thirdparty recipe."); + String thirdPartyUserId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyUserId", + ValueType.STRING, true, String.class, errors, " for a thirdparty recipe."); email = validateAndNormaliseEmail(email, errors); thirdPartyId = validateAndNormaliseThirdPartyId(thirdPartyId, errors); thirdPartyUserId = validateAndNormaliseThirdPartyUserId(thirdPartyUserId, errors); - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, email, null, null, thirdPartyId, thirdPartyUserId, null)); + loginMethods.add(new LoginMethod(normalisedTenantIds, recipeId, isVerified, isPrimary, + timeJoinedInMSSinceEpoch, email, null, null, thirdPartyId, thirdPartyUserId, null)); } else if ("passwordless".equals(recipeId)) { - String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); - String phoneNumber = parseAndValidateFieldType(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, false, String.class, errors, " for a passwordless recipe."); + String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, false, + String.class, errors, " for a passwordless recipe."); + String phoneNumber = parseAndValidateFieldType(jsonLoginMethodObj, "phoneNumber", ValueType.STRING, + false, String.class, errors, " for a passwordless recipe."); email = validateAndNormaliseEmail(email, errors); phoneNumber = validateAndNormalisePhoneNumber(phoneNumber, errors); - loginMethods.add(new LoginMethod(tenantId, recipeId, isVerified, isPrimary, timeJoinedInMSSinceEpoch, email, null, null, null, null, phoneNumber)); + if (email == null && phoneNumber == null) { + errors.add("Either email or phoneNumber is required for a passwordless recipe."); + } + + loginMethods.add(new LoginMethod(normalisedTenantIds, recipeId, isVerified, isPrimary, + timeJoinedInMSSinceEpoch, email, null, null, null, null, phoneNumber)); } } return loginMethods; } - private static String validateAndNormaliseExternalUserId(String externalUserId, List errors) { - if (externalUserId == null ) { + private static String validateAndNormaliseExternalUserId(String externalUserId, Set allExternalUserIds, + List errors) { + if (externalUserId == null) { return null; } @@ -187,6 +242,10 @@ private static String validateAndNormaliseExternalUserId(String externalUserId, errors.add("externalUserId " + externalUserId + " is too long. Max length is 128."); } + if (!allExternalUserIds.add(externalUserId)) { + errors.add("externalUserId " + externalUserId + " is not unique. It is already used by another user."); + } + // We just trim the externalUserId as per the UpdateExternalUserIdInfoAPI.java return externalUserId.trim(); } @@ -207,7 +266,7 @@ private static String validateAndNormaliseUserRole(String role, String[] allUser } private static String validateAndNormaliseTotpSecretKey(String secretKey, List errors) { - if (secretKey == null ) { + if (secretKey == null) { return null; } @@ -215,7 +274,7 @@ private static String validateAndNormaliseTotpSecretKey(String secretKey, List e } private static String validateAndNormaliseTotpDeviceName(String deviceName, List errors) { - if (deviceName == null ) { + if (deviceName == null) { return null; } @@ -287,7 +346,28 @@ private static String validateAndNormaliseRecipeId(String recipeId, List return recipeId; } - private static String validateAndNormaliseTenantId(Main main, AppIdentifier appIdentifier, String tenantId, String recipeId, List errors) + private static List validateAndNormaliseTenantIds(Main main, AppIdentifier appIdentifier, + JsonArray tenantIds, List errors, String errorSuffix) + throws StorageQueryException, TenantOrAppNotFoundException { + if (tenantIds == null) { + return List.of(TenantIdentifier.DEFAULT_TENANT_ID); // Default to DEFAULT_TENANT_ID ("public") + } + + List normalisedTenantIds = new ArrayList<>(); + + for (JsonElement tenantIdEl : tenantIds) { + String tenantId = tenantIdEl.getAsString(); + tenantId = validateAndNormaliseTenantId(main, appIdentifier, tenantId, errors, errorSuffix); + + if (tenantId != null) { + normalisedTenantIds.add(tenantId); + } + } + return normalisedTenantIds; + } + + private static String validateAndNormaliseTenantId(Main main, AppIdentifier appIdentifier, String tenantId, + List errors, String errorSuffix) throws StorageQueryException, TenantOrAppNotFoundException { if (tenantId == null || tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) { return tenantId; @@ -296,9 +376,9 @@ private static String validateAndNormaliseTenantId(Main main, AppIdentifier appI if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures()) .noneMatch(t -> t == EE_FEATURES.MULTI_TENANCY)) { errors.add("Multitenancy must be enabled before importing users to a different tenant."); - return tenantId; + return null; } - + // We make the tenantId lowercase while parsing from the request in WebserverAPI.java String normalisedTenantId = tenantId.trim().toLowerCase(); TenantConfig[] allTenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main); @@ -307,7 +387,8 @@ private static String validateAndNormaliseTenantId(Main main, AppIdentifier appI .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); if (!validTenantIds.contains(normalisedTenantId)) { - errors.add("Invalid tenantId: " + tenantId + " for " + recipeId + " recipe."); + errors.add("Invalid tenantId: " + tenantId + errorSuffix); + return null; } return normalisedTenantId; } @@ -352,21 +433,25 @@ private static String validateAndNormaliseEmail(String email, List error return Utils.normaliseEmail(email); } - private static CoreConfig.PASSWORD_HASHING_ALG validateAndNormaliseHashingAlgorithm(String hashingAlgorithm, List errors) { + private static CoreConfig.PASSWORD_HASHING_ALG validateAndNormaliseHashingAlgorithm(String hashingAlgorithm, + List errors) { if (hashingAlgorithm == null) { return null; } - + try { // We trim the hashingAlgorithm and make it uppercase as per the ImportUserWithPasswordHashAPI.java return CoreConfig.PASSWORD_HASHING_ALG.valueOf(hashingAlgorithm.trim().toUpperCase()); } catch (IllegalArgumentException e) { - errors.add("Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"); + errors.add( + "Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!"); return null; } } - private static String validateAndNormalisePasswordHash(Main main, AppIdentifier appIdentifier, CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm, String passwordHash, List errors) throws TenantOrAppNotFoundException { + private static String validateAndNormalisePasswordHash(Main main, AppIdentifier appIdentifier, + CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm, String passwordHash, List errors) + throws TenantOrAppNotFoundException { if (hashingAlgorithm == null || passwordHash == null) { return passwordHash; } @@ -374,13 +459,14 @@ private static String validateAndNormalisePasswordHash(Main main, AppIdentifier if (passwordHash.length() > 256) { errors.add("passwordHash is too long. Max length is 256."); } - + // We trim the passwordHash and validate it as per ImportUserWithPasswordHashAPI.java passwordHash = passwordHash.trim(); try { - PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, passwordHash, hashingAlgorithm); - } catch (UnsupportedPasswordHashingFormatException e) { + PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, passwordHash, + hashingAlgorithm); + } catch (UnsupportedPasswordHashingFormatException e) { errors.add(e.getMessage()); } @@ -397,8 +483,8 @@ private static String validateAndNormaliseThirdPartyId(String thirdPartyId, List } // We don't perform any normalisation on the thirdPartyId in SignInUpAPI.java - return thirdPartyId; - } + return thirdPartyId; + } private static String validateAndNormaliseThirdPartyUserId(String thirdPartyUserId, List errors) { if (thirdPartyUserId == null) { @@ -410,8 +496,8 @@ private static String validateAndNormaliseThirdPartyUserId(String thirdPartyUser } // We don't perform any normalisation on the thirdPartyUserId in SignInUpAPI.java - return thirdPartyUserId; - } + return thirdPartyUserId; + } private static String validateAndNormalisePhoneNumber(String phoneNumber, List errors) { if (phoneNumber == null) { @@ -426,4 +512,40 @@ private static String validateAndNormalisePhoneNumber(String phoneNumber, List userRoles, List loginMethods, List errors) + throws TenantOrAppNotFoundException { + if (loginMethods == null) { + return; + } + + // First validate that tenantIds provided for userRoles also exist in the loginMethods + if (userRoles != null) { + for (UserRole userRole : userRoles) { + for (String tenantId : userRole.tenantIds) { + if (!tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID) && loginMethods.stream() + .noneMatch(loginMethod -> loginMethod.tenantIds.contains(tenantId))) { + errors.add("TenantId " + tenantId + " for a user role does not exist in loginMethods."); + } + } + } + } + + // Now validate that all the tenants share the same storage + String commonTenantUserPoolId = null; + for (LoginMethod loginMethod : loginMethods) { + for (String tenantId : loginMethod.tenantIds) { + TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), + appIdentifier.getAppId(), tenantId); + Storage storage = StorageLayer.getStorage(tenantIdentifier, main); + String tenantUserPoolId = storage.getUserPoolId(); + + if (commonTenantUserPoolId == null) { + commonTenantUserPoolId = tenantUserPoolId; + } else if (!commonTenantUserPoolId.equals(tenantUserPoolId)) { + errors.add("All tenants for a user must share the same storage."); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/io/supertokens/cronjobs/bulkimport/ProcessBulkImportUsers.java b/src/main/java/io/supertokens/cronjobs/bulkimport/ProcessBulkImportUsers.java new file mode 100644 index 000000000..f62c92759 --- /dev/null +++ b/src/main/java/io/supertokens/cronjobs/bulkimport/ProcessBulkImportUsers.java @@ -0,0 +1,504 @@ +/* +* Copyright (c) 2024, 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.cronjobs.bulkimport; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.supertokens.Main; +import io.supertokens.ResourceDistributor; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.config.Config; +import io.supertokens.cronjobs.CronTask; +import io.supertokens.cronjobs.CronTaskTest; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.EmailPassword.ImportUserResponse; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithEmailAlreadyExistsException; +import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithPhoneNumberAlreadyExistsException; +import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.RestartFlowException; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.StorageUtils; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.UserRole; +import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; +import io.supertokens.pluginInterface.exceptions.DbInitException; +import io.supertokens.pluginInterface.exceptions.InvalidConfigException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; +import io.supertokens.pluginInterface.sqlStorage.SQLStorage; +import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; +import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; +import io.supertokens.pluginInterface.totp.exception.DeviceAlreadyExistsException; +import io.supertokens.pluginInterface.useridmapping.exception.UnknownSuperTokensUserIdException; +import io.supertokens.pluginInterface.useridmapping.exception.UserIdMappingAlreadyExistsException; +import io.supertokens.pluginInterface.userroles.exception.UnknownRoleException; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.thirdparty.ThirdParty.SignInUpResponse; +import io.supertokens.totp.Totp; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.usermetadata.UserMetadata; +import io.supertokens.userroles.UserRoles; +import jakarta.servlet.ServletException; + +public class ProcessBulkImportUsers extends CronTask { + + public static final String RESOURCE_KEY = "io.supertokens.ee.cronjobs.ProcessBulkImportUsers"; + private Map userPoolToStorageMap = new HashMap<>(); + + private ProcessBulkImportUsers(Main main, List> tenantsInfo) { + super("ProcessBulkImportUsers", main, tenantsInfo, true); + } + + public static ProcessBulkImportUsers init(Main main, List> tenantsInfo) { + return (ProcessBulkImportUsers) main.getResourceDistributor() + .setResource(new TenantIdentifier(null, null, null), RESOURCE_KEY, + new ProcessBulkImportUsers(main, tenantsInfo)); + } + + @Override + protected void doTaskPerApp(AppIdentifier app) + throws TenantOrAppNotFoundException, StorageQueryException, InvalidConfigException, IOException, + DbInitException { + + if (StorageLayer.getBaseStorage(main).getType() != STORAGE_TYPE.SQL) { + return; + } + + BulkImportSQLStorage bulkImportSQLStorage = (BulkImportSQLStorage) StorageLayer + .getStorage(app.getAsPublicTenantIdentifier(), main); + + AppIdentifier appIdentifier = new AppIdentifier(app.getConnectionUriDomain(), app.getAppId()); + + List users = bulkImportSQLStorage.getBulkImportUsersForProcessing(appIdentifier, + BulkImport.PROCESS_USERS_BATCH_SIZE); + + for (BulkImportUser user : users) { + processUser(appIdentifier, user); + } + + closeAllProxyStorages(); + } + + @Override + public int getIntervalTimeSeconds() { + if (Main.isTesting) { + Integer interval = CronTaskTest.getInstance(main).getIntervalInSeconds(RESOURCE_KEY); + if (interval != null) { + return interval; + } + } + return BulkImport.PROCESS_USERS_INTERVAL; + } + + @Override + public int getInitialWaitTimeSeconds() { + // We are setting a non-zero initial wait for tests to avoid race condition with the beforeTest process that deletes data in the storage layer + if (Main.isTesting) { + return 5; + } + return 0; + } + + private Storage getProxyStorage(TenantIdentifier tenantIdentifier) + throws InvalidConfigException, IOException, TenantOrAppNotFoundException, DbInitException { + String userPoolId = StorageLayer.getStorage(tenantIdentifier, main).getUserPoolId(); + if (userPoolToStorageMap.containsKey(userPoolId)) { + return userPoolToStorageMap.get(userPoolId); + } + + SQLStorage bulkImportProxyStorage = (SQLStorage) StorageLayer.getNewBulkImportProxyStorageInstance(main, + Config.getBaseConfigAsJsonObject(main), tenantIdentifier, true); + + userPoolToStorageMap.put(userPoolId, bulkImportProxyStorage); + bulkImportProxyStorage.initStorage(true); + return bulkImportProxyStorage; + } + + public Storage[] getAllProxyStoragesForApp(Main main, AppIdentifier appIdentifier) + throws TenantOrAppNotFoundException, InvalidConfigException, IOException, DbInitException { + List allProxyStorages = new ArrayList<>(); + + Map resources = main + .getResourceDistributor() + .getAllResourcesWithResourceKey(RESOURCE_KEY); + for (ResourceDistributor.KeyClass key : resources.keySet()) { + if (key.getTenantIdentifier().toAppIdentifier().equals(appIdentifier)) { + allProxyStorages.add(getProxyStorage(key.getTenantIdentifier())); + } + } + return allProxyStorages.toArray(new Storage[0]); + } + + private void closeAllProxyStorages() { + for (Storage storage : userPoolToStorageMap.values()) { + storage.close(); + } + } + + private void processUser(AppIdentifier appIdentifier, BulkImportUser user) + throws TenantOrAppNotFoundException, StorageQueryException, InvalidConfigException, IOException, + DbInitException { + // Since all the tenants of a user must share the storage, we will just use the + // storage of the first tenantId of the first loginMethod + + TenantIdentifier firstTenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), + appIdentifier.getAppId(), user.loginMethods.get(0).tenantIds.get(0)); + + SQLStorage bulkImportProxyStorage = (SQLStorage) getProxyStorage(firstTenantIdentifier); + + LoginMethod primaryLM = getPrimaryLoginMethod(user); + + try { + bulkImportProxyStorage.startTransaction(con -> { + for (LoginMethod lm : user.loginMethods) { + processUserLoginMethod(appIdentifier, bulkImportProxyStorage, lm); + } + + createPrimaryUserAndLinkAccounts(main, appIdentifier, bulkImportProxyStorage, user, primaryLM); + createUserIdMapping(main, appIdentifier, user, primaryLM); + verifyEmailForAllLoginMethods(appIdentifier, con, bulkImportProxyStorage, user.loginMethods); + createTotpDevices(main, appIdentifier, bulkImportProxyStorage, user.totpDevices, primaryLM); + createUserMetadata(appIdentifier, bulkImportProxyStorage, user, primaryLM); + createUserRoles(main, appIdentifier, bulkImportProxyStorage, user); + + ((BulkImportSQLStorage) bulkImportProxyStorage).deleteBulkImportUser_Transaction(appIdentifier, con, + user.id); + + // We need to commit the transaction manually because we have overridden that in the proxy storage + try { + Connection connection = (Connection) con.getConnection(); + connection.commit(); + connection.setAutoCommit(true); + } catch (SQLException e) { + throw new StorageTransactionLogicException(e); + } + + return null; + }); + } catch (StorageTransactionLogicException e) { + handleProcessUserExceptions(appIdentifier, user, (BulkImportSQLStorage) bulkImportProxyStorage, e); + } + } + + private void handleProcessUserExceptions(AppIdentifier appIdentifier, BulkImportUser user, + BulkImportSQLStorage bulkImportSQLStorage, Exception e) + throws StorageQueryException { + + // Java doesn't allow us to reassign local variables inside a lambda expression + // so we have to use an array. + String[] errorMessage = { e.getMessage() }; + + if (e instanceof StorageTransactionLogicException) { + StorageTransactionLogicException exception = (StorageTransactionLogicException) e; + errorMessage[0] = exception.actualException.getMessage(); + } + + String[] userId = { user.id }; + + try { + bulkImportSQLStorage.startTransaction(con -> { + bulkImportSQLStorage.updateBulkImportUserStatus_Transaction(appIdentifier, con, userId, + BULK_IMPORT_USER_STATUS.FAILED, errorMessage[0]); + + // We need to commit the transaction manually because we have overridden that in the proxy storage + try { + Connection connection = (Connection) con.getConnection(); + connection.commit(); + connection.setAutoCommit(true); + } catch (SQLException ex) { + throw new StorageTransactionLogicException(ex); + } + return null; + }); + } catch (StorageTransactionLogicException e1) { + throw new StorageQueryException(e1.actualException); + } + } + + private void processUserLoginMethod(AppIdentifier appIdentifier, Storage storage, + LoginMethod lm) throws StorageTransactionLogicException { + String firstTenant = lm.tenantIds.get(0); + + TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), + appIdentifier.getAppId(), firstTenant); + + if (lm.recipeId.equals("emailpassword")) { + processEmailPasswordLoginMethod(tenantIdentifier, storage, lm); + } else if (lm.recipeId.equals("thirdparty")) { + processThirdPartyLoginMethod(tenantIdentifier, storage, lm); + } else if (lm.recipeId.equals("passwordless")) { + processPasswordlessLoginMethod(tenantIdentifier, storage, lm); + } else { + throw new StorageTransactionLogicException( + new IllegalArgumentException("Unknown recipeId " + lm.recipeId + " for loginMethod ")); + } + + associateUserToTenants(main, appIdentifier, storage, lm, firstTenant); + } + + private void processEmailPasswordLoginMethod(TenantIdentifier tenantIdentifier, Storage storage, + LoginMethod lm) throws StorageTransactionLogicException { + try { + ImportUserResponse userInfo = EmailPassword.createUserWithPasswordHash(tenantIdentifier, storage, lm.email, + lm.passwordHash, lm.timeJoinedInMSSinceEpoch); + + lm.superTokensOrExternalUserId = userInfo.user.getSupertokensUserId(); + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(e); + } catch (DuplicateEmailException e) { + throw new StorageTransactionLogicException( + new Exception("A user with email " + lm.email + " already exists")); + } + } + + private void processThirdPartyLoginMethod(TenantIdentifier tenantIdentifier, Storage storage, LoginMethod lm) + throws StorageTransactionLogicException { + try { + SignInUpResponse userInfo = ThirdParty.createThirdPartyUser( + tenantIdentifier, storage, lm.thirdPartyId, lm.thirdPartyUserId, lm.email, + lm.timeJoinedInMSSinceEpoch); + + lm.superTokensOrExternalUserId = userInfo.user.getSupertokensUserId(); + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(e); + } catch (DuplicateThirdPartyUserException e) { + throw new StorageTransactionLogicException(new Exception("A user with thirdPartyId " + lm.thirdPartyId + + " and thirdPartyUserId " + lm.thirdPartyUserId + " already exists")); + } + } + + private void processPasswordlessLoginMethod(TenantIdentifier tenantIdentifier, Storage storage, LoginMethod lm) + throws StorageTransactionLogicException { + try { + AuthRecipeUserInfo userInfo = Passwordless.createPasswordlessUser(tenantIdentifier, storage, lm.email, + lm.phoneNumber, lm.timeJoinedInMSSinceEpoch); + + lm.superTokensOrExternalUserId = userInfo.getSupertokensUserId(); + } catch (StorageQueryException | TenantOrAppNotFoundException | RestartFlowException e) { + throw new StorageTransactionLogicException(e); + } + } + + private void associateUserToTenants(Main main, AppIdentifier appIdentifier, Storage storage, LoginMethod lm, + String firstTenant) throws StorageTransactionLogicException { + for (String tenantId : lm.tenantIds) { + try { + if (tenantId.equals(firstTenant)) { + continue; + } + + TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), + appIdentifier.getAppId(), tenantId); + Multitenancy.addUserIdToTenant(main, tenantIdentifier, storage, lm.superTokensOrExternalUserId); + } catch (TenantOrAppNotFoundException | UnknownUserIdException | StorageQueryException + | FeatureNotEnabledException | DuplicateEmailException | DuplicatePhoneNumberException + | DuplicateThirdPartyUserException | AnotherPrimaryUserWithPhoneNumberAlreadyExistsException + | AnotherPrimaryUserWithEmailAlreadyExistsException + | AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException e) { + throw new StorageTransactionLogicException(e); + } + } + } + + private void createPrimaryUserAndLinkAccounts(Main main, + AppIdentifier appIdentifier, Storage storage, BulkImportUser user, LoginMethod primaryLM) + throws StorageTransactionLogicException { + if (user.loginMethods.size() == 1) { + return; + } + + try { + AuthRecipe.createPrimaryUser(main, appIdentifier, storage, primaryLM.superTokensOrExternalUserId); + } catch (TenantOrAppNotFoundException | FeatureNotEnabledException | StorageQueryException e) { + throw new StorageTransactionLogicException(e); + } catch (UnknownUserIdException e) { + throw new StorageTransactionLogicException(new Exception( + "We tried to create the primary user for the userId " + primaryLM.superTokensOrExternalUserId + + " but it doesn't exist. This should not happen. Please contact support.")); + } catch (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException + | AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + throw new StorageTransactionLogicException( + new Exception(e.getMessage() + " This should not happen. Please contact support.")); + } + + for (LoginMethod lm : user.loginMethods) { + try { + if (lm.superTokensOrExternalUserId.equals(primaryLM.superTokensOrExternalUserId)) { + continue; + } + + AuthRecipe.linkAccounts(main, appIdentifier, storage, lm.superTokensOrExternalUserId, + primaryLM.superTokensOrExternalUserId); + + } catch (TenantOrAppNotFoundException | FeatureNotEnabledException | StorageQueryException e) { + throw new StorageTransactionLogicException(e); + } catch (UnknownUserIdException e) { + throw new StorageTransactionLogicException( + new Exception("We tried to link the userId " + lm.superTokensOrExternalUserId + + " to the primary userId " + primaryLM.superTokensOrExternalUserId + + " but it doesn't exist. This should not happen. Please contact support.")); + } catch (InputUserIdIsNotAPrimaryUserException e) { + throw new StorageTransactionLogicException( + new Exception("We tried to link the userId " + lm.superTokensOrExternalUserId + + " to the primary userId " + primaryLM.superTokensOrExternalUserId + + " but it is not a primary user. This should not happen. Please contact support.")); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException + | RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException e) { + throw new StorageTransactionLogicException( + new Exception(e.getMessage() + " This should not happen. Please contact support.")); + } + } + } + + private void createUserIdMapping(Main main, AppIdentifier appIdentifier, + BulkImportUser user, LoginMethod primaryLM) throws StorageTransactionLogicException { + if (user.externalUserId != null) { + try { + UserIdMapping.createUserIdMapping( + appIdentifier, getAllProxyStoragesForApp(main, appIdentifier), + primaryLM.superTokensOrExternalUserId, user.externalUserId, + null, false, true); + + primaryLM.superTokensOrExternalUserId = user.externalUserId; + } catch (StorageQueryException | ServletException | TenantOrAppNotFoundException | InvalidConfigException + | IOException | DbInitException e) { + throw new StorageTransactionLogicException(e); + } catch (UserIdMappingAlreadyExistsException e) { + throw new StorageTransactionLogicException( + new Exception("A user with externalId " + user.externalUserId + " already exists")); + } catch (UnknownSuperTokensUserIdException e) { + throw new StorageTransactionLogicException( + new Exception("We tried to create the externalUserId mapping for the superTokenUserId " + + primaryLM.superTokensOrExternalUserId + + " but it doesn't exist. This should not happen. Please contact support.")); + } + } + } + + private void createUserMetadata(AppIdentifier appIdentifier, Storage storage, BulkImportUser user, + LoginMethod primaryLM) throws StorageTransactionLogicException { + if (user.userMetadata != null) { + try { + UserMetadata.updateUserMetadata(appIdentifier, storage, primaryLM.superTokensOrExternalUserId, + user.userMetadata); + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(e); + } + } + } + + private void createUserRoles(Main main, AppIdentifier appIdentifier, Storage storage, + BulkImportUser user) throws StorageTransactionLogicException { + if (user.userRoles != null) { + for (UserRole userRole : user.userRoles) { + try { + for (String tenantId : userRole.tenantIds) { + TenantIdentifier tenantIdentifier = new TenantIdentifier( + appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), + tenantId); + + UserRoles.addRoleToUser(main, tenantIdentifier, storage, user.externalUserId, userRole.role); + } + } catch (TenantOrAppNotFoundException | StorageQueryException e) { + throw new StorageTransactionLogicException(e); + } catch (UnknownRoleException e) { + throw new StorageTransactionLogicException(new Exception("Role " + userRole.role + + " does not exist! You need pre-create the role before assigning it to the user.")); + } + } + } + } + + private void verifyEmailForAllLoginMethods(AppIdentifier appIdentifier, TransactionConnection con, Storage storage, + List loginMethods) throws StorageTransactionLogicException { + + for (LoginMethod lm : loginMethods) { + try { + + TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), + appIdentifier.getAppId(), lm.tenantIds.get(0)); + + EmailVerificationSQLStorage emailVerificationSQLStorage = StorageUtils + .getEmailVerificationStorage(storage); + emailVerificationSQLStorage + .updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con, + lm.superTokensOrExternalUserId, lm.email, true); + } catch (TenantOrAppNotFoundException | StorageQueryException e) { + throw new StorageTransactionLogicException(e); + } + } + } + + private void createTotpDevices(Main main, AppIdentifier appIdentifier, Storage storage, + List totpDevices, LoginMethod primaryLM) throws StorageTransactionLogicException { + for (TotpDevice totpDevice : totpDevices) { + try { + Totp.createDevice(main, appIdentifier, storage, primaryLM.superTokensOrExternalUserId, + totpDevice.deviceName, totpDevice.skew, totpDevice.period, totpDevice.secretKey, + true, System.currentTimeMillis()); + } catch (TenantOrAppNotFoundException | StorageQueryException | FeatureNotEnabledException e) { + throw new StorageTransactionLogicException(e); + } catch (DeviceAlreadyExistsException e) { + throw new StorageTransactionLogicException( + new Exception("A totp device with name " + totpDevice.deviceName + " already exists")); + } + } + } + + // Returns the primary loginMethod of the user. If no loginMethod is marked as + // primary, then the oldest loginMethod is returned. + private BulkImportUser.LoginMethod getPrimaryLoginMethod(BulkImportUser user) { + BulkImportUser.LoginMethod oldestLM = user.loginMethods.get(0); + for (BulkImportUser.LoginMethod lm : user.loginMethods) { + if (lm.isPrimary) { + return lm; + } + + if (lm.timeJoinedInMSSinceEpoch < oldestLM.timeJoinedInMSSinceEpoch) { + oldestLM = lm; + } + } + return oldestLM; + } +} diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index c92fae912..ea29f5956 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -183,41 +183,55 @@ public static ImportUserResponse importUserWithPasswordHash(TenantIdentifier ten tenantIdentifier.toAppIdentifier(), main, passwordHash, hashingAlgorithm); - while (true) { - String userId = Utils.getUUID(); + EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage); + + ImportUserResponse response = null; + try { long timeJoined = System.currentTimeMillis(); + response = createUserWithPasswordHash(tenantIdentifier, storage, email, passwordHash, timeJoined); + } catch (DuplicateEmailException e) { + AuthRecipeUserInfo[] allUsers = epStorage.listPrimaryUsersByEmail(tenantIdentifier, email); + AuthRecipeUserInfo userInfoToBeUpdated = null; + LoginMethod loginMethod = null; + for (AuthRecipeUserInfo currUser : allUsers) { + for (LoginMethod currLM : currUser.loginMethods) { + if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.tenantIds.contains(tenantIdentifier.getTenantId())) { + userInfoToBeUpdated = currUser; + loginMethod = currLM; + break; + } + } + } - EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage); + if (userInfoToBeUpdated != null) { + LoginMethod finalLoginMethod = loginMethod; + epStorage.startTransaction(con -> { + epStorage.updateUsersPassword_Transaction(tenantIdentifier.toAppIdentifier(), con, + finalLoginMethod.getSupertokensUserId(), passwordHash); + return null; + }); + response = new ImportUserResponse(true, userInfoToBeUpdated); + } + } + return response; + } + + public static ImportUserResponse createUserWithPasswordHash( + TenantIdentifier tenantIdentifier, + Storage storage, + @Nonnull String email, + @Nonnull String passwordHash, @Nullable long timeJoined) + throws StorageQueryException, DuplicateEmailException, TenantOrAppNotFoundException { + EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage); + while (true) { + String userId = Utils.getUUID(); try { - AuthRecipeUserInfo userInfo = epStorage.signUp(tenantIdentifier, userId, email, passwordHash, - timeJoined); + AuthRecipeUserInfo userInfo = null; + userInfo = epStorage.signUp(tenantIdentifier, userId, email, passwordHash, timeJoined); return new ImportUserResponse(false, userInfo); } catch (DuplicateUserIdException e) { // we retry with a new userId - } catch (DuplicateEmailException e) { - AuthRecipeUserInfo[] allUsers = epStorage.listPrimaryUsersByEmail(tenantIdentifier, email); - AuthRecipeUserInfo userInfoToBeUpdated = null; - LoginMethod loginMethod = null; - for (AuthRecipeUserInfo currUser : allUsers) { - for (LoginMethod currLM : currUser.loginMethods) { - if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.tenantIds.contains(tenantIdentifier.getTenantId())) { - userInfoToBeUpdated = currUser; - loginMethod = currLM; - break; - } - } - } - - if (userInfoToBeUpdated != null) { - LoginMethod finalLoginMethod = loginMethod; - epStorage.startTransaction(con -> { - epStorage.updateUsersPassword_Transaction(tenantIdentifier.toAppIdentifier(), con, - finalLoginMethod.getSupertokensUserId(), passwordHash); - return null; - }); - return new ImportUserResponse(true, userInfoToBeUpdated); - } } } } diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index a395238eb..fefaea483 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -135,6 +135,11 @@ public void constructor(String processId, boolean silent, boolean isTesting) { Start.isTesting = isTesting; } + @Override + public Storage createBulkImportProxyStorageInstance() { + return this; + } + @Override public STORAGE_TYPE getType() { return STORAGE_TYPE.SQL; diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index ba76d7f45..e9752911c 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -456,52 +456,36 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, } if (user == null) { - while (true) { - try { - String userId = Utils.getUUID(); - long timeJoined = System.currentTimeMillis(); - user = passwordlessStorage.createUser(tenantIdentifier, userId, consumedDevice.email, - consumedDevice.phoneNumber, timeJoined); + long timeJoined = System.currentTimeMillis(); + user = createPasswordlessUser(tenantIdentifier, storage, consumedDevice.email, + consumedDevice.phoneNumber, timeJoined); - // Set email as verified, if using email - if (setEmailVerified && consumedDevice.email != null) { + // 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 { - 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; - } - throw new StorageQueryException(e); + 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; } - - 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.. + throw new StorageQueryException(e); } } + + return new ConsumeCodeResponse(true, user, consumedDevice.email, consumedDevice.phoneNumber, consumedDevice); } else { if (setEmailVerified && consumedDevice.email != null) { // Set email verification @@ -541,6 +525,29 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifier tenantIdentifier, return new ConsumeCodeResponse(false, user, consumedDevice.email, consumedDevice.phoneNumber, consumedDevice); } + public static AuthRecipeUserInfo createPasswordlessUser(TenantIdentifier tenantIdentifier, Storage storage, + String email, String phoneNumber, long timeJoined) + throws TenantOrAppNotFoundException, StorageQueryException, RestartFlowException { + PasswordlessSQLStorage passwordlessStorage = StorageUtils.getPasswordlessStorage(storage); + + while (true) { + try { + String userId = Utils.getUUID(); + return passwordlessStorage.createUser(tenantIdentifier, userId, email, phoneNumber, timeJoined); + } 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.. + } + } + } + @TestOnly public static void removeCode(Main main, String codeId) throws StorageQueryException, StorageTransactionLogicException { diff --git a/src/main/java/io/supertokens/storageLayer/StorageLayer.java b/src/main/java/io/supertokens/storageLayer/StorageLayer.java index 5145e7c1c..711767703 100644 --- a/src/main/java/io/supertokens/storageLayer/StorageLayer.java +++ b/src/main/java/io/supertokens/storageLayer/StorageLayer.java @@ -56,6 +56,14 @@ public Storage getUnderlyingStorage() { } public static Storage getNewStorageInstance(Main main, JsonObject config, TenantIdentifier tenantIdentifier, boolean doNotLog) throws InvalidConfigException { + return getNewInstance(main, config, tenantIdentifier, doNotLog, false); + } + + public static Storage getNewBulkImportProxyStorageInstance(Main main, JsonObject config, TenantIdentifier tenantIdentifier, boolean doNotLog) throws InvalidConfigException { + return getNewInstance(main, config, tenantIdentifier, doNotLog, true); + } + + private static Storage getNewInstance(Main main, JsonObject config, TenantIdentifier tenantIdentifier, boolean doNotLog, boolean isBulkImportProxy) throws InvalidConfigException { Storage result; if (StorageLayer.ucl == null) { result = new Start(main); @@ -75,7 +83,11 @@ public static Storage getNewStorageInstance(Main main, JsonObject config, Tenant } if (storageLayer != null && !main.isForceInMemoryDB() && (storageLayer.canBeUsed(config) || CLIOptions.get(main).isForceNoInMemoryDB())) { - result = storageLayer; + if (isBulkImportProxy) { + result = storageLayer.createBulkImportProxyStorageInstance(); + } else { + result = storageLayer; + } } else { result = new Start(main); } diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index d49f0a93c..3628aab9d 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -206,22 +206,12 @@ private static SignInUpResponse signInUpHelper(TenantIdentifier tenantIdentifier while (true) { // loop for sign in + sign up - while (true) { - // loop for sign up - String userId = Utils.getUUID(); - long timeJoined = System.currentTimeMillis(); + long timeJoined = System.currentTimeMillis(); - try { - AuthRecipeUserInfo createdUser = tpStorage.signUp(tenantIdentifier, userId, email, - new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId), timeJoined); - - return new SignInUpResponse(true, createdUser); - } catch (DuplicateUserIdException e) { - // we try again.. - } catch (DuplicateThirdPartyUserException e) { - // we try to sign in - break; - } + try { + return createThirdPartyUser( tenantIdentifier, storage, thirdPartyId, thirdPartyUserId, email, timeJoined); + } catch (DuplicateThirdPartyUserException e) { + // The user already exists, we will try to update the email if needed below } // we try to get user and update their email @@ -341,6 +331,25 @@ private static SignInUpResponse signInUpHelper(TenantIdentifier tenantIdentifier } } + public static SignInUpResponse createThirdPartyUser(TenantIdentifier tenantIdentifier, Storage storage, + String thirdPartyId, String thirdPartyUserId, String email, long timeJoined) + throws StorageQueryException, TenantOrAppNotFoundException, DuplicateThirdPartyUserException { + ThirdPartySQLStorage tpStorage = StorageUtils.getThirdPartyStorage(storage); + + while (true) { + // loop for sign up + String userId = Utils.getUUID(); + + try { + AuthRecipeUserInfo createdUser = tpStorage.signUp(tenantIdentifier, userId, email, + new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId), timeJoined); + return new SignInUpResponse(true, createdUser); + } catch (DuplicateUserIdException e) { + // we try again.. + } + } + } + @Deprecated public static AuthRecipeUserInfo getUser(AppIdentifier appIdentifier, Storage storage, String userId) throws StorageQueryException { diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java index 590c7b563..bc3810ef1 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java @@ -18,7 +18,9 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -83,7 +85,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se AppIdentifier appIdentifier = null; Storage storage = null; - + try { appIdentifier = getAppIdentifier(req); storage = enforcePublicTenantAndGetPublicTenantStorage(req); @@ -126,7 +128,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S errorResponseJson.addProperty("error", errorMsg); throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); } - + AppIdentifier appIdentifier = null; Storage storage = null; @@ -146,11 +148,12 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S } JsonArray errorsJson = new JsonArray(); - ArrayList usersToAdd = new ArrayList<>(); + Set allExternalUserIds = new HashSet<>(); + List usersToAdd = new ArrayList<>(); for (int i = 0; i < users.size(); i++) { try { - BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifier, users.get(i).getAsJsonObject(), Utils.getUUID(), allUserRoles); + BulkImportUser user = BulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifier, users.get(i).getAsJsonObject(), Utils.getUUID(), allUserRoles, allExternalUserIds); usersToAdd.add(user); } catch (io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException e) { JsonObject errorObj = new JsonObject(); @@ -212,7 +215,7 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws AppIdentifier appIdentifier = null; Storage storage = null; - + try { appIdentifier = getAppIdentifier(req); storage = enforcePublicTenantAndGetPublicTenantStorage(req); @@ -244,4 +247,4 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws throw new ServletException(e); } } -} +} \ No newline at end of file diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java index 07dc835ad..ca0be8534 100644 --- a/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java @@ -164,7 +164,7 @@ public void testGetUsersStatusFilter() throws Exception { String[] userIds = users.stream().map(user -> user.id).toArray(String[]::new); storage.startTransaction(con -> { - storage.updateBulkImportUserStatus_Transaction(appIdentifier, con, userIds, BULK_IMPORT_USER_STATUS.PROCESSING); + storage.updateBulkImportUserStatus_Transaction(appIdentifier, con, userIds, BULK_IMPORT_USER_STATUS.PROCESSING, null); storage.commitTransaction(con); return null; }); @@ -182,7 +182,7 @@ public void testGetUsersStatusFilter() throws Exception { String[] userIds = users.stream().map(user -> user.id).toArray(String[]::new); storage.startTransaction(con -> { - storage.updateBulkImportUserStatus_Transaction(appIdentifier, con, userIds, BULK_IMPORT_USER_STATUS.FAILED); + storage.updateBulkImportUserStatus_Transaction(appIdentifier, con, userIds, BULK_IMPORT_USER_STATUS.FAILED, null); storage.commitTransaction(con); return null; }); diff --git a/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java index 1aecc66c6..6b822d610 100644 --- a/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java @@ -26,6 +26,7 @@ import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod; import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.UserRole; public class BulkImportTestUtils { public static List generateBulkImportUser(int numberOfUsers) { @@ -39,15 +40,18 @@ public static List generateBulkImportUser(int numberOfUsers) { JsonObject userMetadata = parser.parse("{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}").getAsJsonObject(); - List userRoles = new ArrayList<>(); + List userRoles = new ArrayList<>(); + userRoles.add(new UserRole("role1", List.of("public"))); + userRoles.add(new UserRole("role2", List.of("public"))); List totpDevices = new ArrayList<>(); totpDevices.add(new TotpDevice("secretKey", 30, 1, "deviceName")); List loginMethods = new ArrayList<>(); - loginMethods.add(new LoginMethod("public", "emailpassword", true, true, 0, email, "$2a", "BCRYPT", null, null, null)); - loginMethods.add(new LoginMethod("public", "thirdparty", true, false, 0, email, null, null, "thirdPartyId", "thirdPartyUserId", null)); - loginMethods.add(new LoginMethod("public", "passwordless", true, false, 0, email, null, null, null, null, "+911234567890")); + long currentTimeMillis = System.currentTimeMillis(); + loginMethods.add(new LoginMethod(List.of("public", "t1"), "emailpassword", true, true, currentTimeMillis, email, "$2a", "BCRYPT", null, null, null)); + loginMethods.add(new LoginMethod(List.of("public", "t1"), "thirdparty", true, false, currentTimeMillis, email, null, null, "thirdPartyId" + i, "thirdPartyUserId" + i, null)); + loginMethods.add(new LoginMethod(List.of("public", "t1"), "passwordless", true, false, currentTimeMillis, email, null, null, null, null, null)); users.add(new BulkImportUser(id, externalId, userMetadata, userRoles, totpDevices, loginMethods)); } return users; diff --git a/src/test/java/io/supertokens/test/bulkimport/ProcessBulkImportUsersCronJobTest.java b/src/test/java/io/supertokens/test/bulkimport/ProcessBulkImportUsersCronJobTest.java new file mode 100644 index 000000000..62d241679 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/ProcessBulkImportUsersCronJobTest.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2024, 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.bulkimport; + +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.UserPaginationContainer; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.cronjobs.CronTaskTest; +import io.supertokens.cronjobs.bulkimport.ProcessBulkImportUsers; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; +import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice; +import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; +import io.supertokens.pluginInterface.exceptions.InvalidConfigException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.EmailPasswordConfig; +import io.supertokens.pluginInterface.multitenancy.PasswordlessConfig; +import io.supertokens.pluginInterface.multitenancy.TenantConfig; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.ThirdPartyConfig; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.totp.TOTPDevice; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.TestingProcessManager.TestingProcess; +import io.supertokens.test.Utils; +import io.supertokens.thirdparty.InvalidProviderConfigException; +import io.supertokens.totp.Totp; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.usermetadata.UserMetadata; +import io.supertokens.userroles.UserRoles; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import com.google.gson.JsonObject; + +import static io.supertokens.test.bulkimport.BulkImportTestUtils.generateBulkImportUser; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.List; + +public class ProcessBulkImportUsersCronJobTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void shouldProcessBulkImportUsers() throws Exception { + TestingProcess process = startCronProcess(); + Main main = process.getProcess(); + + // Create user roles before inserting bulk users + { + UserRoles.createNewRoleOrModifyItsPermissions(main, "role1", null); + UserRoles.createNewRoleOrModifyItsPermissions(main, "role2", null); + } + + createTenants(main); + + BulkImportSQLStorage storage = (BulkImportSQLStorage) StorageLayer.getStorage(main); + AppIdentifier appIdentifier = new AppIdentifier(null, null); + + int usersCount = 1; + List users = generateBulkImportUser(usersCount); + BulkImport.addUsers(appIdentifier, storage, users); + + BulkImportUser bulkImportUser = users.get(0); + + // Thread.sleep(600000); + Thread.sleep(6000); + + List usersAfterProcessing = storage.getBulkImportUsers(appIdentifier, null, null, + null, null); + + System.out.println("Users after processing: " + usersAfterProcessing.size()); + assertEquals(0, usersAfterProcessing.size()); + + UserPaginationContainer container = AuthRecipe.getUsers(main, 100, "ASC", null, null, null); + assertEquals(usersCount, container.users.length); + + UserIdMapping.populateExternalUserIdForUsers(storage, container.users); + + for (AuthRecipeUserInfo user : container.users) { + for (LoginMethod lm1 : user.loginMethods) { + bulkImportUser.loginMethods.forEach(lm2 -> { + if (lm2.recipeId.equals(lm1.recipeId.toString())) { + assertLoginMethodEquals(lm1, lm2); + } + }); + } + + JsonObject createdUserMetadata = UserMetadata.getUserMetadata(main, user.getSupertokensOrExternalUserId()); + assertEquals(bulkImportUser.userMetadata, createdUserMetadata); + + String[] createdUserRoles = UserRoles.getRolesForUser(main, user.getSupertokensOrExternalUserId()); + String[] bulkImportUserRoles = bulkImportUser.userRoles.stream().map(r -> r.role).toArray(String[]::new); + assertArrayEquals(bulkImportUserRoles, createdUserRoles); + + assertEquals(bulkImportUser.externalUserId, user.getSupertokensOrExternalUserId()); + + + TOTPDevice[] createdTotpDevices = Totp.getDevices(main, user.getSupertokensOrExternalUserId()); + assertTotpDevicesEquals(createdTotpDevices, bulkImportUser.totpDevices.toArray(new TotpDevice[0])); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldDeleteEverythingFromtheDBIfAnythingFails() throws Exception { + // Creating a non-existing user role will result in an error. + // Since, user role creation happens at the last step of the bulk import process, everything should be deleted from the DB. + + TestingProcess process = startCronProcess(); + Main main = process.getProcess(); + + createTenants(main); + + BulkImportSQLStorage storage = (BulkImportSQLStorage) StorageLayer.getStorage(main); + AppIdentifier appIdentifier = new AppIdentifier(null, null); + + List users = generateBulkImportUser(1); + BulkImport.addUsers(appIdentifier, storage, users); + + Thread.sleep(6000); + + List usersAfterProcessing = storage.getBulkImportUsers(appIdentifier, null, null, + null, null); + + assertEquals(1, usersAfterProcessing.size()); + + assertEquals(BULK_IMPORT_USER_STATUS.FAILED, usersAfterProcessing.get(0).status); + assertEquals("Role role1 does not exist! You need pre-create the role before assigning it to the user.", + usersAfterProcessing.get(0).errorMessage); + + UserPaginationContainer container = AuthRecipe.getUsers(main, 100, "ASC", null, null, null); + assertEquals(0, container.users.length); + } + + @Test + public void shouldThrowTenantDoesNotExistError() throws Exception { + TestingProcess process = startCronProcess(); + Main main = process.getProcess(); + + BulkImportSQLStorage storage = (BulkImportSQLStorage) StorageLayer.getStorage(main); + AppIdentifier appIdentifier = new AppIdentifier(null, null); + + List users = generateBulkImportUser(1); + BulkImport.addUsers(appIdentifier, storage, users); + + Thread.sleep(6000); + + List usersAfterProcessing = storage.getBulkImportUsers(appIdentifier, null, null, + null, null); + + assertEquals(1, usersAfterProcessing.size()); + assertEquals(BULK_IMPORT_USER_STATUS.FAILED, usersAfterProcessing.get(0).status); + assertEquals( + "Tenant with the following connectionURIDomain, appId and tenantId combination not found: (, public, t1)", + usersAfterProcessing.get(0).errorMessage); + } + + private TestingProcess startCronProcess() throws InterruptedException { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + + Main main = process.getProcess(); + + FeatureFlagTestContent.getInstance(main) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[] { + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY, EE_FEATURES.MFA }); + + CronTaskTest.getInstance(main).setIntervalInSeconds(ProcessBulkImportUsers.RESOURCE_KEY, 100000); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(main).getType() != STORAGE_TYPE.SQL) { + return null; + } + + return process; + } + + private void assertLoginMethodEquals(LoginMethod lm1, + io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod lm2) { + assertEquals(lm1.email, lm2.email); + assertEquals(lm1.verified, lm2.isVerified); + assertTrue(lm2.tenantIds.containsAll(lm1.tenantIds) && lm1.tenantIds.containsAll(lm2.tenantIds)); + + switch (lm2.recipeId) { + case "emailpassword": + assertEquals(lm1.passwordHash, lm2.passwordHash); + break; + case "thirdparty": + assertEquals(lm1.thirdParty.id, lm2.thirdPartyId); + assertEquals(lm1.thirdParty.userId, lm2.thirdPartyUserId); + break; + case "passwordless": + assertEquals(lm1.phoneNumber, lm2.phoneNumber); + break; + default: + break; + } + } + + private void assertTotpDevicesEquals(TOTPDevice[] createdTotpDevices, TotpDevice[] bulkImportTotpDevices) { + assertEquals(createdTotpDevices.length, bulkImportTotpDevices.length); + for (int i = 0; i < createdTotpDevices.length; i++) { + assertEquals(createdTotpDevices[i].deviceName, bulkImportTotpDevices[i].deviceName); + assertEquals(createdTotpDevices[i].period, bulkImportTotpDevices[i].period); + assertEquals(createdTotpDevices[i].secretKey, bulkImportTotpDevices[i].secretKey); + assertEquals(createdTotpDevices[i].skew, bulkImportTotpDevices[i].skew); + } + } + + private void createTenants(Main main) + throws StorageQueryException, TenantOrAppNotFoundException, InvalidProviderConfigException, + FeatureNotEnabledException, IOException, InvalidConfigException, + CannotModifyBaseConfigException, BadPermissionException { + { // tenant 1 (t1 in the same storage as public tenant) + TenantIdentifier tenantIdentifier = new TenantIdentifier(null, null, "t1"); + + Multitenancy.addNewOrUpdateAppOrTenant( + main, + new TenantIdentifier(null, null, null), + new TenantConfig( + tenantIdentifier, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject())); + } + { // tenant 2 (t2 in the different storage than public tenant) + TenantIdentifier tenantIdentifier = new TenantIdentifier(null, null, "t2"); + + JsonObject config = new JsonObject(); + + StorageLayer.getStorage(new TenantIdentifier(null, null, null), main) + .modifyConfigToAddANewUserPoolForTesting(config, 1); + Multitenancy.addNewOrUpdateAppOrTenant( + main, + new TenantIdentifier(null, null, null), + new TenantConfig( + tenantIdentifier, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, config)); + } + } +} diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index b3e37cbf3..3303ebca7 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -18,7 +18,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import java.io.IOException; import java.util.HashMap; import java.util.UUID; @@ -33,14 +35,28 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import io.supertokens.Main; import io.supertokens.ProcessState; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.exceptions.InvalidConfigException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.EmailPasswordConfig; +import io.supertokens.pluginInterface.multitenancy.PasswordlessConfig; +import io.supertokens.pluginInterface.multitenancy.TenantConfig; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.ThirdPartyConfig; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.InvalidProviderConfigException; import io.supertokens.userroles.UserRoles; public class AddBulkImportUsersTest { @@ -72,6 +88,11 @@ public void shouldThrow400Error() throws Exception { return; } + // Create user roles + { + UserRoles.createNewRoleOrModifyItsPermissions(process.getProcess(), "role1", null); + } + String genericErrMsg = "Data has missing or invalid fields. Please check the users field for more details."; // users is required in the json body @@ -82,6 +103,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -93,6 +115,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -107,6 +130,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -119,6 +143,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -131,6 +156,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -146,11 +172,27 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"externalUserId should be of type string.\",\"userRoles should be of type array of object.\",\"totpDevices should be of type array of object.\",\"loginMethods is required.\"]}]}"); + } + // Non-unique externalUserIds + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"externalUserId\":\"id1\"}, {\"externalUserId\":\"id1\"}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); assertEquals(responseString, - "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"externalUserId should be of type string.\",\"userRoles should be of type array of string.\",\"totpDevices should be of type array of object.\",\"loginMethods is required.\"]}]}"); + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"loginMethods is required.\"]},{\"index\":1,\"errors\":[\"loginMethods is required.\",\"externalUserId id1 is not unique. It is already used by another user.\"]}]}"); } // secretKey is required in totpDevices try { @@ -160,20 +202,38 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); assertEquals(responseString, "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"secretKey is required for a totp device.\",\"loginMethods is required.\"]}]}"); } - // Invalid role (does not exist) + // Invalid role (tenantIds is required) try { JsonObject request = new JsonParser() - .parse("{\"users\":[{\"userRoles\":[\"role5\"]}]}") + .parse("{\"users\":[{\"userRoles\":[{\"role\":\"role1\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"tenantIds is required for a user role.\",\"loginMethods is required.\"]}]}"); + } + // Invalid role (role doesn't exist) + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"userRoles\":[{\"role\":\"role5\", \"tenantIds\": [\"public\"]}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -186,16 +246,17 @@ public void shouldThrow400Error() throws Exception { { try { JsonObject request = new JsonParser().parse( - "{\"users\":[{\"loginMethods\":[{\"recipeId\":[],\"tenantId\":[],\"isPrimary\":[],\"isVerified\":[],\"timeJoinedInMSSinceEpoch\":[]}]}]}") + "{\"users\":[{\"loginMethods\":[{\"recipeId\":[],\"tenantIds\":{},\"isPrimary\":[],\"isVerified\":[],\"timeJoinedInMSSinceEpoch\":[]}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); assertEquals(responseString, - "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"recipeId should be of type string for a loginMethod.\",\"tenantId should be of type string for a loginMethod.\",\"isVerified should be of type boolean for a loginMethod.\",\"isPrimary should be of type boolean for a loginMethod.\",\"timeJoinedInMSSinceEpoch should be of type integer for a loginMethod\"]}]}"); + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"recipeId should be of type string for a loginMethod.\",\"tenantIds should be of type array of string for a loginMethod.\",\"isVerified should be of type boolean for a loginMethod.\",\"isPrimary should be of type boolean for a loginMethod.\",\"timeJoinedInMSSinceEpoch should be of type integer for a loginMethod\"]}]}"); } } // Invalid recipeId @@ -207,6 +268,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -223,6 +285,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -237,6 +300,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -251,6 +315,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -267,6 +332,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -281,6 +347,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -297,6 +364,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -311,46 +379,70 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); assertEquals(responseString, - "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"email should be of type string for a passwordless recipe.\",\"phoneNumber should be of type string for a passwordless recipe.\"]}]}"); + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"email should be of type string for a passwordless recipe.\",\"phoneNumber should be of type string for a passwordless recipe.\",\"Either email or phoneNumber is required for a passwordless recipe.\"]}]}"); } } // Validate tenantId { - // CASE 1: Different tenantId when multitenancy is not enabled + // CASE 1: Invalid tenantId when multitenancy is not enabled try { JsonObject request = new JsonParser().parse( - "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") + "{\"users\":[{\"loginMethods\":[{\"tenantIds\":[\"invalid\"],\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); assertEquals(responseString, "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Multitenancy must be enabled before importing users to a different tenant.\"]}]}"); } - // CASE 2: Different tenantId when multitenancy is enabled + // CASE 2: Invalid tenantId when multitenancy is enabled try { FeatureFlagTestContent.getInstance(process.getProcess()) .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY}); JsonObject request = new JsonParser().parse( - "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") + "{\"users\":[{\"loginMethods\":[{\"tenantIds\":[\"invalid\"],\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") .getAsJsonObject(); HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); assertEquals(responseString, "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Invalid tenantId: invalid for passwordless recipe.\"]}]}"); } + // CASE 3. Two more tenants do not share the same storage + try { + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY}); + + createTenants(process.getProcess()); + + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"tenantIds\":[\"public\"],\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}, {\"tenantIds\":[\"t2\"],\"recipeId\":\"thirdparty\", \"email\":\"johndoe@gmail.com\", \"thirdPartyId\":\"id\", \"thirdPartyUserId\":\"id\"}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"All tenants for a user must share the same storage.\"]}]}"); + } } // No two loginMethods can have isPrimary as true { @@ -361,6 +453,7 @@ public void shouldThrow400Error() throws Exception { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -372,9 +465,10 @@ public void shouldThrow400Error() throws Exception { { try { JsonObject request = generateUsersJson(0); - HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/users", - request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -385,9 +479,10 @@ public void shouldThrow400Error() throws Exception { { try { JsonObject request = generateUsersJson(10001); - HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/bulk-import/users", - request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); @@ -495,15 +590,16 @@ public static JsonObject generateUsersJson(int numberOfUsers) { user.addProperty("externalUserId", UUID.randomUUID().toString()); user.add("userMetadata", parser.parse("{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}")); - user.add("userRoles", parser.parse("[\"role1\", \"role2\"]")); + user.add("userRoles", parser.parse("[{\"role\":\"role1\", \"tenantIds\": [\"public\"]},{\"role\":\"role2\", \"tenantIds\": [\"public\"]}]")); user.add("totpDevices", parser.parse("[{\"secretKey\":\"secretKey\",\"deviceName\":\"deviceName\"}]")); + JsonArray tenanatIds = parser.parse("[\"public\"]").getAsJsonArray(); String email = " johndoe+" + i + "@gmail.com "; JsonArray loginMethodsArray = new JsonArray(); - loginMethodsArray.add(createEmailLoginMethod(email)); - loginMethodsArray.add(createThirdPartyLoginMethod(email)); - loginMethodsArray.add(createPasswordlessLoginMethod(email)); + loginMethodsArray.add(createEmailLoginMethod(email, tenanatIds)); + loginMethodsArray.add(createThirdPartyLoginMethod(email, tenanatIds)); + loginMethodsArray.add(createPasswordlessLoginMethod(email, tenanatIds)); user.add("loginMethods", loginMethodsArray); usersArray.add(user); @@ -513,9 +609,9 @@ public static JsonObject generateUsersJson(int numberOfUsers) { return userJsonObject; } - private static JsonObject createEmailLoginMethod(String email) { + private static JsonObject createEmailLoginMethod(String email, JsonArray tenantIds) { JsonObject loginMethod = new JsonObject(); - loginMethod.addProperty("tenantId", "public"); + loginMethod.add("tenantIds", tenantIds); loginMethod.addProperty("email", email); loginMethod.addProperty("recipeId", "emailpassword"); loginMethod.addProperty("passwordHash", "$argon2d$v=19$m=12,t=3,p=1$aGI4enNvMmd0Zm0wMDAwMA$r6p7qbr6HD+8CD7sBi4HVw"); @@ -526,9 +622,9 @@ private static JsonObject createEmailLoginMethod(String email) { return loginMethod; } - private static JsonObject createThirdPartyLoginMethod(String email) { + private static JsonObject createThirdPartyLoginMethod(String email, JsonArray tenantIds) { JsonObject loginMethod = new JsonObject(); - loginMethod.addProperty("tenantId", "public"); + loginMethod.add("tenantIds", tenantIds); loginMethod.addProperty("recipeId", "thirdparty"); loginMethod.addProperty("email", email); loginMethod.addProperty("thirdPartyId", "google"); @@ -539,9 +635,9 @@ private static JsonObject createThirdPartyLoginMethod(String email) { return loginMethod; } - private static JsonObject createPasswordlessLoginMethod(String email) { + private static JsonObject createPasswordlessLoginMethod(String email, JsonArray tenantIds) { JsonObject loginMethod = new JsonObject(); - loginMethod.addProperty("tenantId", "public"); + loginMethod.add("tenantIds", tenantIds); loginMethod.addProperty("email", email); loginMethod.addProperty("recipeId", "passwordless"); loginMethod.addProperty("phoneNumber", "+91-9999999999"); @@ -550,4 +646,47 @@ private static JsonObject createPasswordlessLoginMethod(String email) { loginMethod.addProperty("timeJoinedInMSSinceEpoch", 0); return loginMethod; } + + private void createTenants(Main main) + throws StorageQueryException, TenantOrAppNotFoundException, InvalidProviderConfigException, + FeatureNotEnabledException, IOException, InvalidConfigException, + CannotModifyBaseConfigException, BadPermissionException { + // User pool 1 - (null, null, null), (null, null, t1) + // User pool 2 - (null, null, t2) + + { // tenant 1 + TenantIdentifier tenantIdentifier = new TenantIdentifier(null, null, "t1"); + + Multitenancy.addNewOrUpdateAppOrTenant( + main, + new TenantIdentifier(null, null, null), + new TenantConfig( + tenantIdentifier, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, new JsonObject() + ) + ); + } + { // tenant 2 + JsonObject config = new JsonObject(); + TenantIdentifier tenantIdentifier = new TenantIdentifier(null, null, "t2"); + + StorageLayer.getStorage(new TenantIdentifier(null, null, null), main) + .modifyConfigToAddANewUserPoolForTesting(config, 1); + + Multitenancy.addNewOrUpdateAppOrTenant( + main, + new TenantIdentifier(null, null, null), + new TenantConfig( + tenantIdentifier, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + null, null, config + ) + ); + } + } } diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java index c3499f0a1..db2fe1707 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java @@ -139,8 +139,8 @@ public void shouldReturn200Response() throws Exception { return; } - AppIdentifier appIdentifier = new AppIdentifier(null, null); BulkImportStorage storage = (BulkImportStorage) StorageLayer.getStorage(process.main); + AppIdentifier appIdentifier = new AppIdentifier(null, null); // Insert users List users = generateBulkImportUser(5); diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java index 8b40f96bf..181bcd336 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import java.util.HashMap; import java.util.Map; @@ -72,7 +73,7 @@ public void shouldReturn400Error() throws Exception { HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); - + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { assertEquals(400, e.statusCode); assertEquals( @@ -86,7 +87,7 @@ public void shouldReturn400Error() throws Exception { HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); - + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { assertEquals(400, e.statusCode); assertEquals("Http error. Status Code: 400. Message: limit must a positive integer with min value 1", @@ -99,7 +100,7 @@ public void shouldReturn400Error() throws Exception { HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); - + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { assertEquals(400, e.statusCode); assertEquals("Http error. Status Code: 400. Message: Max limit allowed is 500", e.getMessage()); @@ -111,7 +112,7 @@ public void shouldReturn400Error() throws Exception { HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/bulk-import/users", params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); - + fail("The API should have thrown an error"); } catch (io.supertokens.test.httpRequest.HttpResponseException e) { assertEquals(400, e.statusCode); assertEquals("Http error. Status Code: 400. Message: invalid pagination token", e.getMessage()); @@ -151,7 +152,7 @@ public void shouldReturn200Response() throws Exception { assertEquals(1, bulkImportUsers.size()); JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); bulkImportUserJson.get("status").getAsString().equals("NEW"); - BulkImportUser.fromTesting_fromJson(bulkImportUserJson).toRawDataForDbStorage().equals(rawData); + BulkImportUser.forTesting_fromJson(bulkImportUserJson).toRawDataForDbStorage().equals(rawData); process.kill(); Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));

m%R%QK2U1}UTEdpD}Hiwjj7k7*3oy0{a2r+PY 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 z%0A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