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/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 679236a42..fdb82869b 100644 Binary files a/cli/jar/cli.jar and b/cli/jar/cli.jar differ diff --git a/downloader/jar/downloader.jar b/downloader/jar/downloader.jar index 4c8bac4ca..b39a17b48 100644 Binary files a/downloader/jar/downloader.jar and b/downloader/jar/downloader.jar differ diff --git a/ee/jar/ee.jar b/ee/jar/ee.jar index 1795fb9d7..e3537716e 100644 Binary files a/ee/jar/ee.jar and b/ee/jar/ee.jar differ diff --git a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java index cac2c5cb9..9c1610259 100644 --- a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java +++ b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java @@ -267,8 +267,7 @@ private JsonObject getMultiTenancyStats() return stats; } - private JsonObject getAccountLinkingStats() throws StorageQueryException { - // TODO: Active users are present only on public tenant and MFA users may be present on different storages + private JsonObject getAccountLinkingStats() throws StorageQueryException, TenantOrAppNotFoundException { JsonObject result = new JsonObject(); Storage[] storages = StorageLayer.getStoragesForApp(main, this.appIdentifier); boolean usesAccountLinking = false; diff --git a/jar/core-7.0.18.jar b/jar/core-7.0.18.jar deleted file mode 100644 index 4d0695612..000000000 Binary files a/jar/core-7.0.18.jar and /dev/null differ diff --git a/jar/core-8.0.0.jar b/jar/core-8.0.0.jar new file mode 100644 index 000000000..fc8067d57 Binary files /dev/null and b/jar/core-8.0.0.jar differ 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/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/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/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java new file mode 100644 index 000000000..8bfabc7e1 --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -0,0 +1,90 @@ +/* + * 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.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.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.utils.Utils; + + +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +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_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 { + while (true) { + try { + 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 + for (BulkImportUser user : users) { + user.id = Utils.getUUID(); + } + } + } + } + + 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 = bulkImportStorage + .getBulkImportUsers(appIdentifier, limit + 1, status, null, null); + } else { + BulkImportUserPaginationToken tokenInfo = BulkImportUserPaginationToken.extractTokenInfo(paginationToken); + users = bulkImportStorage + .getBulkImportUsers(appIdentifier, limit + 1, status, tokenInfo.bulkImportUserId, tokenInfo.createdAt); + } + + String nextPaginationToken = null; + int maxLoop = users.size(); + if (users.size() == limit + 1) { + maxLoop = limit; + BulkImportUser user = users.get(limit); + nextPaginationToken = new BulkImportUserPaginationToken(user.id, user.createdAt).generateToken(); + } + + List resultUsers = users.subList(0, maxLoop); + return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken); + } + + 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/bulkimport/BulkImportUserPaginationContainer.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java new file mode 100644 index 000000000..f691c68c3 --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java @@ -0,0 +1,34 @@ +/* + * 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.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; + +public class BulkImportUserPaginationContainer { + public final List users; + public final String nextPaginationToken; + + public BulkImportUserPaginationContainer(@Nonnull List 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..8a492c2ca --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java @@ -0,0 +1,53 @@ +/* + * 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 final long createdAt; + + public BulkImportUserPaginationToken(String bulkImportUserId, long createdAt) { + this.bulkImportUserId = bulkImportUserId; + this.createdAt = createdAt; + } + + public static BulkImportUserPaginationToken extractTokenInfo(String token) throws InvalidTokenException { + try { + String decodedPaginationToken = new String(Base64.getDecoder().decode(token)); + String[] splitDecodedToken = decodedPaginationToken.split(";"); + if (splitDecodedToken.length != 2) { + throw new InvalidTokenException(); + } + String bulkImportUserId = splitDecodedToken[0]; + long createdAt = Long.parseLong(splitDecodedToken[1]); + return new BulkImportUserPaginationToken(bulkImportUserId, createdAt); + } catch (Exception e) { + throw new InvalidTokenException(); + } + } + + public String generateToken() { + return new String(Base64.getEncoder().encode((this.bulkImportUserId + ";" + this.createdAt).getBytes())); + } + + public static class InvalidTokenException extends Exception { + + private static final long serialVersionUID = 6289026174830695478L; + } +} 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..c96a10fa6 --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserUtils.java @@ -0,0 +1,551 @@ +/* + * 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.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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.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; + +import static io.supertokens.utils.JsonValidatorUtils.parseAndValidateFieldType; +import static io.supertokens.utils.JsonValidatorUtils.validateJsonFieldType; + +public class BulkImportUserUtils { + 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, + errors, "."); + JsonObject userMetadata = parseAndValidateFieldType(userData, "userMetadata", ValueType.OBJECT, false, + JsonObject.class, errors, "."); + List userRoles = getParsedUserRoles(main, appIdentifier, userData, allUserRoles, errors); + List totpDevices = getParsedTotpDevices(userData, errors); + List loginMethods = getParsedLoginMethods(main, appIdentifier, userData, errors); + + externalUserId = validateAndNormaliseExternalUserId(externalUserId, allExternalUserIds, errors); + + validateTenantIdsForRoleAndLoginMethods(main, appIdentifier, userRoles, loginMethods, errors); + + if (!errors.isEmpty()) { + throw new InvalidBulkImportDataException(errors); + } + return new BulkImportUser(id, externalUserId, userMetadata, userRoles, totpDevices, loginMethods); + } + + 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; + } + + 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, "."); + + if (jsonTotpDevices == null) { + return null; + } + + List totpDevices = new ArrayList<>(); + for (JsonElement jsonTotpDeviceEl : jsonTotpDevices) { + JsonObject jsonTotpDevice = jsonTotpDeviceEl.getAsJsonObject(); + + String secretKey = parseAndValidateFieldType(jsonTotpDevice, "secretKey", ValueType.STRING, true, + String.class, errors, " for a totp device."); + Integer period = parseAndValidateFieldType(jsonTotpDevice, "period", ValueType.INTEGER, false, + Integer.class, errors, " for a totp device."); + Integer skew = parseAndValidateFieldType(jsonTotpDevice, "skew", ValueType.INTEGER, false, Integer.class, + errors, " for a totp device."); + String deviceName = parseAndValidateFieldType(jsonTotpDevice, "deviceName", ValueType.STRING, false, + String.class, errors, " for a totp device."); + + secretKey = validateAndNormaliseTotpSecretKey(secretKey, errors); + period = validateAndNormaliseTotpPeriod(period, errors); + skew = validateAndNormaliseTotpSkew(skew, errors); + deviceName = validateAndNormaliseTotpDeviceName(deviceName, errors); + + if (secretKey != null && period != null && skew != null) { + totpDevices.add(new TotpDevice(secretKey, period, skew, deviceName)); + } + } + return totpDevices; + } + + 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, "."); + + if (jsonLoginMethods == null) { + return new ArrayList<>(); + } + + if (jsonLoginMethods.size() == 0) { + errors.add("At least one loginMethod is required."); + return new ArrayList<>(); + } + + validateAndNormaliseIsPrimaryField(jsonLoginMethods, errors); + + List loginMethods = new ArrayList<>(); + + for (JsonElement jsonLoginMethod : jsonLoginMethods) { + JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject(); + + 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); + 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."); + + 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(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."); + + email = validateAndNormaliseEmail(email, errors); + thirdPartyId = validateAndNormaliseThirdPartyId(thirdPartyId, errors); + thirdPartyUserId = validateAndNormaliseThirdPartyUserId(thirdPartyUserId, errors); + + 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."); + + email = validateAndNormaliseEmail(email, errors); + phoneNumber = validateAndNormalisePhoneNumber(phoneNumber, errors); + + 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, Set allExternalUserIds, + List errors) { + if (externalUserId == null) { + return null; + } + + if (externalUserId.length() > 255) { + 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(); + } + + 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 + 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) { + 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(Integer period, List errors) { + // 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; + } + + private static Integer validateAndNormaliseTotpSkew(Integer skew, List errors) { + // 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; + } + + 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.trim(); + } + + 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 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; + } + + 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 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); + Set validTenantIds = new HashSet<>(); + Arrays.stream(allTenantConfigs) + .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); + + if (!validTenantIds.contains(normalisedTenantId)) { + errors.add("Invalid tenantId: " + tenantId + errorSuffix); + return null; + } + return normalisedTenantId; + } + + 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(Long timeJoined, List errors) { + // We default timeJoined to currentTime if it is null + if (timeJoined == null) { + return System.currentTimeMillis(); + } + + 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, 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 Utils.normaliseEmail(email); + } + + 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 validateAndNormalisePasswordHash(Main main, AppIdentifier appIdentifier, + CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm, String passwordHash, List errors) + throws TenantOrAppNotFoundException { + 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(); + + try { + PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, passwordHash, + hashingAlgorithm); + } catch (UnsupportedPasswordHashingFormatException e) { + errors.add(e.getMessage()); + } + + return passwordHash; + } + + 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, 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, 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 Utils.normalizeIfPhoneNumber(phoneNumber); + } + + private static void validateTenantIdsForRoleAndLoginMethods(Main main, AppIdentifier appIdentifier, + 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/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/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/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..ea29f5956 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,67 +157,81 @@ 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(); + 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; + } + } + } + + 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); + } + } - EmailPasswordSQLStorage storage = tenantIdentifierWithStorage.getEmailPasswordStorage(); + 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 = storage.signUp(tenantIdentifierWithStorage, 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 = storage.listPrimaryUsersByEmail(tenantIdentifierWithStorage, 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())) { - userInfoToBeUpdated = currUser; - loginMethod = currLM; - break; - } - } - } - - if (userInfoToBeUpdated != null) { - LoginMethod finalLoginMethod = loginMethod; - storage.startTransaction(con -> { - storage.updateUsersPassword_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, - finalLoginMethod.getSupertokensUserId(), passwordHash); - return null; - }); - return new ImportUserResponse(true, userInfoToBeUpdated); - } } } } @@ -230,7 +243,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 +256,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 +297,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 +322,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 +335,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 +348,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 +372,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 +410,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 +428,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 +436,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 +455,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 +473,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 +502,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 +520,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 +537,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 +555,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 +600,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 +640,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 +656,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 +665,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 +696,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 +719,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..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; @@ -1843,7 +1848,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 +1857,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 +1932,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..e9752911c 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,8 +251,8 @@ public static ConsumeCodeResponse consumeCode(Main main, try { Storage storage = StorageLayer.getStorage(main); return consumeCode( - new TenantIdentifierWithStorage(null, null, null, storage), - main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, false, true); + new TenantIdentifier(null, null, null), storage, + main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, false); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } @@ -266,45 +268,45 @@ public static ConsumeCodeResponse consumeCode(Main main, try { Storage storage = StorageLayer.getStorage(main); return consumeCode( - new TenantIdentifierWithStorage(null, null, null, storage), - main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, setEmailVerified, true); + new TenantIdentifier(null, null, null), storage, + main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, setEmailVerified); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } @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, - false, true); + return consumeCode(tenantIdentifier, storage, main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, + false); } - public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, 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, 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(); @@ -337,24 +339,23 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant throw new DeviceIdHashMismatchException(); } - PasswordlessDevice consumedDevice; try { - consumedDevice = passwordlessStorage.startTransaction(con -> { - PasswordlessDevice device = passwordlessStorage.getDevice_Transaction(tenantIdentifierWithStorage, con, + return passwordlessStorage.startTransaction(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 +363,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); @@ -384,12 +385,14 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant throw new StorageTransactionLogicException(new RestartFlowException()); } - if (device.email != null) { - passwordlessStorage.deleteDevicesByEmail_Transaction(tenantIdentifierWithStorage, con, - device.email); - } else if (device.phoneNumber != null) { - passwordlessStorage.deleteDevicesByPhoneNumber_Transaction(tenantIdentifierWithStorage, 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); @@ -407,16 +410,30 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } 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; 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 +441,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; @@ -439,68 +456,48 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } if (user == null) { - if (createRecipeUserIfNotExists) { - while (true) { - try { - String userId = Utils.getUUID(); - long timeJoined = System.currentTimeMillis(); - user = passwordlessStorage.createUser(tenantIdentifierWithStorage, 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 -> { - try { - tenantIdentifierWithStorage.getEmailVerificationStorage() - .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, - finalUser.getSupertokensUserId(), consumedDevice.email, true); - tenantIdentifierWithStorage.getEmailVerificationStorage() - .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); - } - } + long timeJoined = System.currentTimeMillis(); + user = createPasswordlessUser(tenantIdentifier, storage, consumedDevice.email, + consumedDevice.phoneNumber, timeJoined); - 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.. + // 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; } + throw new StorageQueryException(e); } } + + return new ConsumeCodeResponse(true, user, consumedDevice.email, consumedDevice.phoneNumber, consumedDevice); } 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 { 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) { @@ -516,29 +513,54 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } } + // 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(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); } + 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 { 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 +568,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,30 +580,42 @@ 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; }); } + 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 { 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 +627,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 +649,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 +674,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 +699,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 +725,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 +758,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 +780,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 +799,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 +814,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..711767703 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; @@ -55,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); @@ -74,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); } @@ -385,7 +398,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 +411,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..3628aab9d 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,36 +198,25 @@ 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 - while (true) { - // loop for sign up - String userId = Utils.getUUID(); - long timeJoined = System.currentTimeMillis(); - - try { - AuthRecipeUserInfo createdUser = storage.signUp(tenantIdentifierWithStorage, userId, email, - new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId), timeJoined); + long timeJoined = System.currentTimeMillis(); - 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 - 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 +225,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 +255,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 +263,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 +272,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } if (userFromDb1 == null) { - storage.commitTransaction(con); + tpStorage.commitTransaction(con); return null; } @@ -320,11 +310,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 +326,35 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } } - AuthRecipeUserInfo user = getUser(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); + AuthRecipeUserInfo user = getUser(tenantIdentifier, storage, thirdPartyId, thirdPartyUserId); return new SignInUpResponse(false, user); } } + 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(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 +371,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 +387,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/utils/JsonValidatorUtils.java b/src/main/java/io/supertokens/utils/JsonValidatorUtils.java new file mode 100644 index 000000000..6c2f81cf3 --- /dev/null +++ b/src/main/java/io/supertokens/utils/JsonValidatorUtils.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.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 parseAndValidateFieldType(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 INTEGER: + 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; + 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, + INTEGER, + LONG, + BOOLEAN, + OBJECT, + ARRAY_OF_STRING, + ARRAY_OF_OBJECT + } + + 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"; + 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().isBlank(); + 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() + && 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().isBlank()); + default -> false; + }; + } + } + \ No newline at end of file diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 3a593fbb0..96716c746 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -24,9 +24,9 @@ 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.bulkimport.BulkImportAPI; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -144,7 +144,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 " @@ -188,6 +188,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)); @@ -260,6 +261,8 @@ private void setupRoutes() { addAPI(new RequestStatsAPI(main)); + addAPI(new BulkImportAPI(main)); + StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); 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/bulkimport/BulkImportAPI.java b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java new file mode 100644 index 000000000..bc3810ef1 --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/BulkImportAPI.java @@ -0,0 +1,250 @@ +/* + * 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 java.util.HashSet; +import java.util.List; +import java.util.Set; + +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.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.AppIdentifier; +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; + } + + BULK_IMPORT_USER_STATUS status = null; + if (statusString != null) { + try { + 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!")); + } + } + + AppIdentifier appIdentifier = null; + Storage storage = null; + + try { + appIdentifier = getAppIdentifier(req); + storage = enforcePublicTenantAndGetPublicTenantStorage(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } + + try { + BulkImportUserPaginationContainer users = BulkImport.getUsers(appIdentifier, storage, limit, status, paginationToken); + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + + JsonArray usersJson = new JsonArray(); + for (BulkImportUser 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())); + } + + AppIdentifier appIdentifier = null; + Storage storage = null; + + try { + appIdentifier = getAppIdentifier(req); + storage = enforcePublicTenantAndGetPublicTenantStorage(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } + + String[] allUserRoles = null; + + try { + allUserRoles = StorageUtils.getUserRolesStorage(storage).getRoles(appIdentifier); + } catch (StorageQueryException e) { + throw new ServletException(e); + } + + JsonArray errorsJson = new JsonArray(); + 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, allExternalUserIds); + 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 (StorageQueryException | TenantOrAppNotFoundException 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(appIdentifier, storage, 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_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; + } + + AppIdentifier appIdentifier = null; + Storage storage = null; + + try { + appIdentifier = getAppIdentifier(req); + storage = enforcePublicTenantAndGetPublicTenantStorage(req); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new ServletException(e); + } + + try { + List deletedIds = BulkImport.deleteUsers(appIdentifier, storage, userIds); + + JsonArray deletedIdsJson = new JsonArray(); + JsonArray invalidIds = new JsonArray(); + + for (String userId : userIds) { + if (deletedIds.contains(userId)) { + deletedIdsJson.add(new JsonPrimitive(userId)); + } else { + invalidIds.add(new JsonPrimitive(userId)); + } + } + + JsonObject result = new JsonObject(); + result.add("deletedIds", deletedIdsJson); + result.add("invalidIds", invalidIds); + + super.sendJsonResponse(200, result, resp); + + } catch (StorageQueryException e) { + throw new ServletException(e); + } + } +} \ No newline at end of file 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/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 404947121..a1aa41ee6 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; @@ -64,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); @@ -82,68 +82,42 @@ 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); 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}); + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{consumeCodeResponse.user}); - ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, consumeCodeResponse.user.getSupertokensUserId()); + 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(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{consumeCodeResponse.user}); - - ActiveUsers.updateLastActive(this.getPublicTenantStorage(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/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..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(this.getTenantIdentifierWithStorageFromRequest(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/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/bulkimport/BulkImportTest.java b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java new file mode 100644 index 000000000..ca0be8534 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTest.java @@ -0,0 +1,265 @@ +/* + * 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.BULK_IMPORT_USER_STATUS; +import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +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 AppIdentifier(null, null), storage, users); + + 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) { + BulkImportUser matchingUser = addedUsers.stream() + .filter(addedUser -> user.id.equals(addedUser.id)) + .findFirst() + .orElse(null); + + assertNotNull(matchingUser); + assertEquals(BULK_IMPORT_USER_STATUS.NEW, matchingUser.status); + assertEquals(user.toRawDataForDbStorage(), matchingUser.toRawDataForDbStorage()); + } + + 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); + AppIdentifier appIdentifier = new AppIdentifier(null, null); + BulkImport.addUsers(appIdentifier, storage, users); + + 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) { + BulkImportUser matchingUser = addedUsers.stream() + .filter(addedUser -> user.toRawDataForDbStorage().equals(addedUser.toRawDataForDbStorage())) + .findFirst() + .orElse(null); + + assertNotNull(matchingUser); + assertEquals(BULK_IMPORT_USER_STATUS.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); + AppIdentifier appIdentifier = new AppIdentifier(null, null); + + // Test with status = 'NEW' + { + List users = generateBulkImportUser(10); + BulkImport.addUsers(appIdentifier, storage, users); + + 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(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(appIdentifier, con, userIds, BULK_IMPORT_USER_STATUS.PROCESSING, null); + storage.commitTransaction(con); + return 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(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(appIdentifier, con, userIds, BULK_IMPORT_USER_STATUS.FAILED, null); + storage.commitTransaction(con); + return null; + }); + + List addedUsers = storage.getBulkImportUsers(appIdentifier, null, BULK_IMPORT_USER_STATUS.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 AppIdentifier(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 = Long.compare(user2.createdAt, 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 AppIdentifier(null, null), storage, limit, null, paginationToken); + + for (BulkImportUser actualUser : users.users) { + BulkImportUser expectedUser = sortedUsers.get(indexIntoUsers); + + assertEquals(expectedUser.id, actualUser.id); + assertEquals(expectedUser.status, actualUser.status); + assertEquals(expectedUser.toRawDataForDbStorage(), actualUser.toRawDataForDbStorage()); + 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..6b822d610 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/BulkImportTestUtils.java @@ -0,0 +1,59 @@ +/* + * 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 com.google.gson.JsonParser; + +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) { + 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 = parser.parse("{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}").getAsJsonObject(); + + 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<>(); + 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 new file mode 100644 index 000000000..3303ebca7 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -0,0 +1,692 @@ +/* + * 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 static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.HashMap; +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.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 { + @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; + } + + // 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 + { + // CASE 1: users field is not present + try { + JsonObject request = new JsonObject(); + 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, "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/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, "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/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\":[\"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/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\":[\"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/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\":[\"At least one loginMethod is required.\"]}]}"); + } + } + // Invalid field type of non required fields outside loginMethod + { + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"externalUserId\":[],\"userMetaData\":[],\"userRoles\":{},\"totpDevices\":{}}]}") + .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 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\":[\"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 { + 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); + 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 (tenantIds is required) + try { + JsonObject request = new JsonParser() + .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); + + 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 + { + try { + JsonObject request = new JsonParser().parse( + "{\"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.\",\"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 + { + try { + JsonObject request = new JsonParser() + .parse("{\"users\":[{\"loginMethods\":[{\"recipeId\":\"invalid_recipe_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\":[\"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/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 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/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 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\":\"$2a\",\"hashingAlgorithm\":\"invalid_algorithm\"}]}]}") + .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 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/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 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/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 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/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\":[\"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/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.\",\"Either email or phoneNumber is required for a passwordless recipe.\"]}]}"); + } + } + // Validate tenantId + { + // CASE 1: Invalid tenantId when multitenancy is not enabled + try { + JsonObject request = new JsonParser().parse( + "{\"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: 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\":[{\"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 + { + // CASE 1: email, passwordHash and hashingAlgorithm are not present + try { + 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/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\":[\"No two loginMethods can have isPrimary as true.\"]}]}"); + } + } + // Can't import less than 1 user at a time + { + try { + JsonObject request = generateUsersJson(0); + 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\":\"You need to add at least one user.\"}"); + } + } + // Can't import more than 10000 users at a time + { + try { + JsonObject request = generateUsersJson(10001); + 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\":\"You can only add 10000 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; + } + + // 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", + 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; + } + + // 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", + 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()); + + JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); + + // 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")) { + assertEquals("johndoe+0@gmail.com", loginMethod.get("email").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)); + } + + 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("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, tenanatIds)); + loginMethodsArray.add(createThirdPartyLoginMethod(email, tenanatIds)); + loginMethodsArray.add(createPasswordlessLoginMethod(email, tenanatIds)); + user.add("loginMethods", loginMethodsArray); + + usersArray.add(user); + } + + userJsonObject.add("users", usersArray); + return userJsonObject; + } + + private static JsonObject createEmailLoginMethod(String email, JsonArray tenantIds) { + JsonObject loginMethod = new JsonObject(); + 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"); + loginMethod.addProperty("hashingAlgorithm", "argon2"); + loginMethod.addProperty("isVerified", true); + loginMethod.addProperty("isPrimary", true); + loginMethod.addProperty("timeJoinedInMSSinceEpoch", 0); + return loginMethod; + } + + private static JsonObject createThirdPartyLoginMethod(String email, JsonArray tenantIds) { + JsonObject loginMethod = new JsonObject(); + loginMethod.add("tenantIds", tenantIds); + 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, JsonArray tenantIds) { + JsonObject loginMethod = new JsonObject(); + loginMethod.add("tenantIds", tenantIds); + 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; + } + + 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 new file mode 100644 index 000000000..db2fe1707 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/apis/DeleteBulkImportUsersTest.java @@ -0,0 +1,173 @@ +/* + * 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 static org.junit.Assert.assertTrue; + +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.AppIdentifier; +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 DeleteBulkImportUsersTest { + @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); + AppIdentifier appIdentifier = new AppIdentifier(null, null); + + // Insert users + List users = generateBulkImportUser(5); + BulkImport.addUsers(appIdentifier, storage, users); + + String invalidId = io.supertokens.utils.Utils.getUUID(); + JsonObject request = new JsonObject(); + JsonArray validIds = new JsonArray(); + for (BulkImportUser user : users) { + validIds.add(new JsonPrimitive(user.id)); + } + validIds.add(new JsonPrimitive(invalidId)); + + 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("deletedIds").getAsJsonArray().forEach(id -> { + assertTrue(validIds.contains(id)); + }); + + assertEquals(invalidId, response.get("invalidIds").getAsJsonArray().get(0).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 new file mode 100644 index 000000000..181bcd336 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java @@ -0,0 +1,160 @@ +/* + * 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 static org.junit.Assert.fail; + +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.pluginInterface.bulkimport.BulkImportUser; +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); + 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 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); + 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", + 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); + 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()); + } + + 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); + 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()); + } + + 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/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"); + BulkImportUser.forTesting_fromJson(bulkImportUserJson).toRawDataForDbStorage().equals(rawData); + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} 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/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)); + } +} 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); }