diff --git a/CHANGELOG.md b/CHANGELOG.md index 423fea79..22b76669 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [6.4.0] + +- Adds support for Bulk Import +- Adds `BulkImportUser` class to represent a bulk import user +- Adds `BulkImportStorage` interface +- Adds `DuplicateUserIdException` class +- Adds `createBulkImportProxyStorageInstance` method in `Storage` class +- Adds `closeConnectionForBulkImportProxyStorage`, `commitTransactionForBulkImportProxyStorage`, and `rollbackTransactionForBulkImportProxyStorage` method in `SQLStorage` class +- Adds `BulkImportTransactionRolledBackException` for signaling if the transaction was rolled back by the DBMS + ## [6.3.0] - 2024-10-02 - Adds `OAuthStorage` interface for OAuth Provider support diff --git a/build.gradle b/build.gradle index b945eddd..a13bdd2f 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -version = "6.3.0" +version = "6.4.0" repositories { mavenCentral() diff --git a/src/main/java/io/supertokens/pluginInterface/Storage.java b/src/main/java/io/supertokens/pluginInterface/Storage.java index 22176881..a5adca1d 100644 --- a/src/main/java/io/supertokens/pluginInterface/Storage.java +++ b/src/main/java/io/supertokens/pluginInterface/Storage.java @@ -26,6 +26,7 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import java.util.List; +import java.util.Map; import java.util.Set; public interface Storage { @@ -33,6 +34,8 @@ public interface Storage { // if silent is true, do not log anything out on the console void constructor(String processId, boolean silent, boolean isTesting); + Storage createBulkImportProxyStorageInstance(); + void loadConfig(JsonObject jsonConfig, Set logLevels, TenantIdentifier tenantIdentifier) throws InvalidConfigException; @@ -78,6 +81,9 @@ void setKeyValue(TenantIdentifier tenantIdentifier, String key, KeyValueInfo inf boolean isUserIdBeingUsedInNonAuthRecipe(AppIdentifier appIdentifier, String className, String userId) throws StorageQueryException; + Map> findNonAuthRecipesWhereForUserIdsUsed(AppIdentifier appIdentifier, List userIds) + throws StorageQueryException; + // to be used for testing purposes only. This function will add dummy data to non-auth tables. void addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier tenantIdentifier, String className, String userId) throws StorageQueryException; diff --git a/src/main/java/io/supertokens/pluginInterface/StorageUtils.java b/src/main/java/io/supertokens/pluginInterface/StorageUtils.java index 9a18dbae..b1722a0c 100644 --- a/src/main/java/io/supertokens/pluginInterface/StorageUtils.java +++ b/src/main/java/io/supertokens/pluginInterface/StorageUtils.java @@ -17,6 +17,7 @@ package io.supertokens.pluginInterface; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; +import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage; import io.supertokens.pluginInterface.dashboard.sqlStorage.DashboardSQLStorage; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; @@ -134,6 +135,15 @@ public static MultitenancyStorage getMultitenancyStorage(Storage storage) { return (MultitenancyStorage) storage; } + public static BulkImportSQLStorage getBulkImportStorage(Storage storage) { + if (storage.getType() != STORAGE_TYPE.SQL) { + // we only support SQL for now + throw new UnsupportedOperationException(""); + } + + return (BulkImportSQLStorage) storage; + } + public static OAuthStorage getOAuthStorage(Storage storage) { if (storage.getType() != STORAGE_TYPE.SQL) { // we only support SQL for now diff --git a/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeStorage.java b/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeStorage.java index 701b8130..9eeef263 100644 --- a/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeStorage.java @@ -26,6 +26,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.List; public interface AuthRecipeStorage extends Storage { @@ -45,6 +46,8 @@ AuthRecipeUserInfo[] getUsers(TenantIdentifier tenantIdentifier, @Nonnull Intege boolean doesUserIdExist(TenantIdentifier tenantIdentifierIdentifier, String userId) throws StorageQueryException; + List findExistingUserIds(AppIdentifier appIdentifier, List userIds) throws StorageQueryException; + AuthRecipeUserInfo getPrimaryUserById(AppIdentifier appIdentifier, String userId) throws StorageQueryException; String getPrimaryUserIdStrForUserId(AppIdentifier appIdentifier, String userId) throws StorageQueryException; diff --git a/src/main/java/io/supertokens/pluginInterface/authRecipe/sqlStorage/AuthRecipeSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/authRecipe/sqlStorage/AuthRecipeSQLStorage.java index 1bcb8efb..6e5352f7 100644 --- a/src/main/java/io/supertokens/pluginInterface/authRecipe/sqlStorage/AuthRecipeSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/authRecipe/sqlStorage/AuthRecipeSQLStorage.java @@ -23,12 +23,19 @@ import io.supertokens.pluginInterface.sqlStorage.SQLStorage; import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; +import java.util.List; +import java.util.Map; + public interface AuthRecipeSQLStorage extends AuthRecipeStorage, SQLStorage { AuthRecipeUserInfo getPrimaryUserById_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId) throws StorageQueryException; + List getPrimaryUsersByIds_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + List userIds) + throws StorageQueryException; + // lock order: // - emailpassword table // - thirdparty table @@ -38,6 +45,13 @@ AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(AppIdentifier appIdenti String email) throws StorageQueryException; + //helper method for bulk import + AuthRecipeUserInfo[] listPrimaryUsersByMultipleEmailsOrPhoneNumbersOrThirdparty_Transaction(AppIdentifier appIdentifier, + TransactionConnection con, + List emails, List phones, + Map thirdpartyIdToThirdpartyUserId) + throws StorageQueryException; + // locks only passwordless table AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String phoneNumber) @@ -52,9 +66,15 @@ AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_Transaction(AppIdentifier void makePrimaryUser_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId) throws StorageQueryException; + void makePrimaryUsers_Transaction(AppIdentifier appIdentifier, TransactionConnection con, List userIds) + throws StorageQueryException; + void linkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String recipeUserId, String primaryUserId) throws StorageQueryException; + void linkMultipleAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + Map recipeUserIdByPrimaryUserId) throws StorageQueryException; + void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String primaryUserId, String recipeUserId) throws StorageQueryException; diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java new file mode 100644 index 00000000..492d199f --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportStorage.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.pluginInterface.bulkimport; + +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; + +public interface BulkImportStorage extends NonAuthRecipeStorage { + /** + * Add users to the bulk_import_users table + */ + void addBulkImportUsers(AppIdentifier appIdentifier, List users) + throws StorageQueryException, + TenantOrAppNotFoundException, + io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException; + + /** + * Get users from the bulk_import_users table + */ + List getBulkImportUsers(AppIdentifier appIdentifier, @Nonnull Integer limit, @Nullable BULK_IMPORT_USER_STATUS status, + @Nullable String bulkImportUserId, @Nullable Long createdAt) throws StorageQueryException; + + /** + * Delete users by id from the bulk_import_users table + */ + List deleteBulkImportUsers(AppIdentifier appIdentifier, @Nonnull String[] bulkImportUserIds) throws StorageQueryException; + + /** + * Returns the users from the bulk_import_users table for processing + */ + List getBulkImportUsersAndChangeStatusToProcessing(AppIdentifier appIdentifier, @Nonnull Integer limit) throws StorageQueryException; + + + /** + * Update the bulk_import_user's primary_user_id by bulk_import_user_id + */ + void updateBulkImportUserPrimaryUserId(AppIdentifier appIdentifier, @Nonnull String bulkImportUserId, @Nonnull String primaryUserId) throws StorageQueryException; + + /** + * Returns the count of users from the bulk_import_users table + */ + long getBulkImportUsersCount(AppIdentifier appIdentifier, @Nullable BULK_IMPORT_USER_STATUS status) throws StorageQueryException; + + public enum BULK_IMPORT_USER_STATUS { + NEW, PROCESSING, FAILED + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java new file mode 100644 index 00000000..dc18dc83 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/BulkImportUser.java @@ -0,0 +1,151 @@ +/* + * 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.pluginInterface.bulkimport; + +import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS; + +public class BulkImportUser { + public String id; + public String externalUserId; + public JsonObject userMetadata; + public List userRoles; + public List totpDevices; + public List loginMethods; + + // Following fields come from the DB Record. + public BULK_IMPORT_USER_STATUS status; + public String primaryUserId; + public String errorMessage; + public Long createdAt; + public Long updatedAt; + + private static final Gson gson = new Gson(); + + public BulkImportUser(String id, String externalUserId, JsonObject userMetadata, List userRoles, + List totpDevices, List loginMethods) { + this.id = id; + this.externalUserId = externalUserId; + this.userMetadata = userMetadata; + this.userRoles = userRoles; + this.totpDevices = totpDevices; + this.loginMethods = loginMethods; + } + + public static BulkImportUser forTesting_fromJson(JsonObject jsonObject) { + return gson.fromJson(jsonObject, BulkImportUser.class); + } + + // The bulk_import_users table stores users to be imported via a Cron Job. + // It has a `raw_data` column containing user data in JSON format. + + // The BulkImportUser class represents this `raw_data`, including additional fields like `status`, `createdAt`, and `updatedAt`. + // First, we validate all fields of `raw_data` using the BulkImportUser class, then store this data in the bulk_import_users table. + // This function retrieves the `raw_data` after removing the additional fields. + public String toRawDataForDbStorage() { + JsonObject jsonObject = gson.fromJson(new Gson().toJson(this), JsonObject.class); + jsonObject.remove("status"); + jsonObject.remove("createdAt"); + jsonObject.remove("updatedAt"); + return jsonObject.toString(); + } + + // The bulk_import_users table contains a `raw_data` column with user data in JSON format, along with other columns such as `id`, `status`, `primary_user_id`, and `error_msg` etc. + + // When creating an instance of the BulkImportUser class, the extra fields must be passed separately as they are not part of the `raw_data`. + // This function creates a BulkImportUser instance from a stored bulk_import_user entry. + public static BulkImportUser fromRawDataFromDbStorage(String id, String rawData, BULK_IMPORT_USER_STATUS status, String primaryUserId, String errorMessage, long createdAt, long updatedAt) { + BulkImportUser user = gson.fromJson(rawData, BulkImportUser.class); + user.id = id; + user.status = status; + user.primaryUserId = primaryUserId; + user.errorMessage = errorMessage; + user.createdAt = createdAt; + user.updatedAt = updatedAt; + return user; + } + + public JsonObject toJsonObject() { + return gson.fromJson(gson.toJson(this), JsonObject.class); + } + + public static class UserRole { + public String role; + public List tenantIds; + + public UserRole(String role, List tenantIds) { + this.role = role; + this.tenantIds = tenantIds; + } + } + + public static class TotpDevice { + public String secretKey; + public int period; + public int skew; + public String deviceName; + + public TotpDevice(String secretKey, int period, int skew, String deviceName) { + this.secretKey = secretKey; + this.period = period; + this.skew = skew; + this.deviceName = deviceName; + } + } + + public static class LoginMethod { + public List tenantIds; + public boolean isVerified; + public boolean isPrimary; + public long timeJoinedInMSSinceEpoch; + public String recipeId; + public String email; + public String passwordHash; + public String hashingAlgorithm; + public String plainTextPassword; + public String thirdPartyId; + public String thirdPartyUserId; + public String phoneNumber; + public String superTokensUserId; + public String externalUserId; + + public String getSuperTokenOrExternalUserId() { + return this.externalUserId != null ? this.externalUserId : this.superTokensUserId; + } + + public LoginMethod(List tenantIds, String recipeId, boolean isVerified, boolean isPrimary, + long timeJoinedInMSSinceEpoch, String email, String passwordHash, String hashingAlgorithm, String plainTextPassword, + String thirdPartyId, String thirdPartyUserId, String phoneNumber) { + this.tenantIds = tenantIds; + this.recipeId = recipeId; + this.isVerified = isVerified; + this.isPrimary = isPrimary; + this.timeJoinedInMSSinceEpoch = timeJoinedInMSSinceEpoch; + this.email = email; + this.passwordHash = passwordHash; + this.hashingAlgorithm = hashingAlgorithm; + this.plainTextPassword = plainTextPassword; + this.thirdPartyId = thirdPartyId; + this.thirdPartyUserId = thirdPartyUserId; + this.phoneNumber = phoneNumber; + } + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/ImportUserBase.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/ImportUserBase.java new file mode 100644 index 00000000..fd97cff2 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/ImportUserBase.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.pluginInterface.bulkimport; + +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; + +public class ImportUserBase { + + public String userId; + public String email; + public TenantIdentifier tenantIdentifier; + public long timeJoinedMSSinceEpoch; + + public ImportUserBase(String userId, String email, TenantIdentifier tenantIdentifier, long timeJoinedMSSinceEpoch) { + this.userId = userId; //this will be the supertokens userId. + this.email = email; + this.tenantIdentifier = tenantIdentifier; + this.timeJoinedMSSinceEpoch = timeJoinedMSSinceEpoch; + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/BulkImportBatchInsertException.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/BulkImportBatchInsertException.java new file mode 100644 index 00000000..fb55e18a --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/BulkImportBatchInsertException.java @@ -0,0 +1,40 @@ +/* + * 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.pluginInterface.bulkimport.exceptions; + +import java.util.Map; + +//In case of batch inserting when one +public class BulkImportBatchInsertException extends Exception { + public Map exceptionByUserId; + + public BulkImportBatchInsertException(String message, Map exceptionByPosition) { + super(message); + this.exceptionByUserId = exceptionByPosition; + } + + public BulkImportBatchInsertException(String message, Throwable cause, + Map exceptionByPosition) { + super(message, cause); + this.exceptionByUserId = exceptionByPosition; + } + + public BulkImportBatchInsertException(Throwable cause, Map exceptionByPosition) { + super(cause); + this.exceptionByUserId = exceptionByPosition; + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/BulkImportTransactionRolledBackException.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/BulkImportTransactionRolledBackException.java new file mode 100644 index 00000000..b78afbca --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/BulkImportTransactionRolledBackException.java @@ -0,0 +1,38 @@ +/* + * 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.pluginInterface.bulkimport.exceptions; + +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; + +import java.io.Serial; +/* + The purpose of this exception is to signal for the BulkImport cronjob that the mysql transaction was rolled back. + PostgreSQL supports nested transactions while MySQL doesn't. When encountering a deadlock in the multithreaded + bulkimport process, both DBMS issue a rollback to resolve the deadlock. + In PostgreSQL: the innermost transaction gets reverted and later retried by the java logic. + In MySQL: the rollback causes the whole "big" transaction to revert, but the java logic only retries the innermost + automatically. + This exception is intended for the ProcessBulkImportUsers cronjob, to signal to restart the whole transaction. + */ +public class BulkImportTransactionRolledBackException extends StorageTransactionLogicException { + @Serial + private static final long serialVersionUID = 5196064868023712426L; + + public BulkImportTransactionRolledBackException(Exception e) { + super(e); + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/DuplicateUserIdException.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/DuplicateUserIdException.java new file mode 100644 index 00000000..07070c15 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/exceptions/DuplicateUserIdException.java @@ -0,0 +1,21 @@ +/* + * 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.pluginInterface.bulkimport.exceptions; + +public class DuplicateUserIdException extends Exception { + private static final long serialVersionUID = 6848053563771647272L; +} diff --git a/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java new file mode 100644 index 00000000..d8cb6f3f --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/bulkimport/sqlStorage/BulkImportSQLStorage.java @@ -0,0 +1,39 @@ +/* + * 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.pluginInterface.bulkimport.sqlStorage; + +import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.sqlStorage.SQLStorage; +import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Map; + +public interface BulkImportSQLStorage extends BulkImportStorage, SQLStorage { + + /** + * Update the status of the users in the bulk_import_users table + */ + void updateBulkImportUserStatus_Transaction(AppIdentifier appIdentifier, + TransactionConnection con, @Nonnull String bulkImportUserId, @Nonnull BULK_IMPORT_USER_STATUS status, @Nullable String errorMessage) throws StorageQueryException; + + void updateMultipleBulkImportUsersStatusToError_Transaction(AppIdentifier appIdentifier, + TransactionConnection con, @Nonnull Map bulkImportUserIdToErrorMessage) throws StorageQueryException; +} diff --git a/src/main/java/io/supertokens/pluginInterface/emailpassword/EmailPasswordImportUser.java b/src/main/java/io/supertokens/pluginInterface/emailpassword/EmailPasswordImportUser.java new file mode 100644 index 00000000..00f254df --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/emailpassword/EmailPasswordImportUser.java @@ -0,0 +1,30 @@ +/* + * 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.pluginInterface.emailpassword; + +import io.supertokens.pluginInterface.bulkimport.ImportUserBase; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; + +public class EmailPasswordImportUser extends ImportUserBase { + + public String passwordHash; + + public EmailPasswordImportUser(String userId, String email, String passwordHash, TenantIdentifier tenantId, long timeJoinedInMSSinceEpoch) { + super(userId, email, tenantId, timeJoinedInMSSinceEpoch); + this.passwordHash = passwordHash; + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/emailpassword/sqlStorage/EmailPasswordSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/emailpassword/sqlStorage/EmailPasswordSQLStorage.java index af47334f..d212d8e1 100644 --- a/src/main/java/io/supertokens/pluginInterface/emailpassword/sqlStorage/EmailPasswordSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/emailpassword/sqlStorage/EmailPasswordSQLStorage.java @@ -16,14 +16,18 @@ package io.supertokens.pluginInterface.emailpassword.sqlStorage; +import io.supertokens.pluginInterface.emailpassword.EmailPasswordImportUser; import io.supertokens.pluginInterface.emailpassword.EmailPasswordStorage; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.sqlStorage.SQLStorage; import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; +import java.util.List; + public interface EmailPasswordSQLStorage extends EmailPasswordStorage, SQLStorage { // all password reset related stuff is app wide cause the same user ID can be shared across tenants, @@ -49,4 +53,7 @@ void updateUsersEmail_Transaction(AppIdentifier appIdentifier, TransactionConnec void deleteEmailPasswordUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) throws StorageQueryException; + + void signUpMultipleViaBulkImport_Transaction(TransactionConnection connection, List users) + throws StorageQueryException, StorageTransactionLogicException; } diff --git a/src/main/java/io/supertokens/pluginInterface/emailverification/EmailVerificationStorage.java b/src/main/java/io/supertokens/pluginInterface/emailverification/EmailVerificationStorage.java index 55d8b298..ade6b091 100644 --- a/src/main/java/io/supertokens/pluginInterface/emailverification/EmailVerificationStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/emailverification/EmailVerificationStorage.java @@ -23,6 +23,8 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; +import java.util.Map; + public interface EmailVerificationStorage extends NonAuthRecipeStorage { void addEmailVerificationToken(TenantIdentifier tenantIdentifier, EmailVerificationTokenInfo emailVerificationInfo) @@ -49,4 +51,7 @@ EmailVerificationTokenInfo[] getAllEmailVerificationTokenInfoForUser(TenantIdent void updateIsEmailVerifiedToExternalUserId(AppIdentifier appIdentifier, String supertokensUserId, String externalUserId) throws StorageQueryException; + + void updateMultipleIsEmailVerifiedToExternalUserIds(AppIdentifier appIdentifier, + Map supertokensUserIdToExternalUserId) throws StorageQueryException; } \ No newline at end of file diff --git a/src/main/java/io/supertokens/pluginInterface/emailverification/sqlStorage/EmailVerificationSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/emailverification/sqlStorage/EmailVerificationSQLStorage.java index 877fb902..bde59d29 100644 --- a/src/main/java/io/supertokens/pluginInterface/emailverification/sqlStorage/EmailVerificationSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/emailverification/sqlStorage/EmailVerificationSQLStorage.java @@ -25,6 +25,8 @@ import io.supertokens.pluginInterface.sqlStorage.SQLStorage; import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; +import java.util.Map; + public interface EmailVerificationSQLStorage extends EmailVerificationStorage, SQLStorage { EmailVerificationTokenInfo[] getAllEmailVerificationTokenInfoForUser_Transaction(TenantIdentifier tenantIdentifier, @@ -42,6 +44,10 @@ void updateIsEmailVerified_Transaction(AppIdentifier appIdentifier, TransactionC boolean isEmailVerified) throws StorageQueryException, TenantOrAppNotFoundException; + void updateMultipleIsEmailVerified_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + Map emailToUserId, boolean isEmailVerified) + throws StorageQueryException, TenantOrAppNotFoundException; + void deleteEmailVerificationUserInfo_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException; diff --git a/src/main/java/io/supertokens/pluginInterface/passwordless/PasswordlessImportUser.java b/src/main/java/io/supertokens/pluginInterface/passwordless/PasswordlessImportUser.java new file mode 100644 index 00000000..4afb2cf9 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/passwordless/PasswordlessImportUser.java @@ -0,0 +1,30 @@ +/* + * 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.pluginInterface.passwordless; + +import io.supertokens.pluginInterface.bulkimport.ImportUserBase; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; + +public class PasswordlessImportUser extends ImportUserBase { + + public String phoneNumber; + + public PasswordlessImportUser(String userId, String phoneNumber, String email, TenantIdentifier tenantIdentifier, long timeJoinedInMSSinceEpoch) { + super(userId, email, tenantIdentifier, timeJoinedInMSSinceEpoch); + this.phoneNumber = phoneNumber; + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/passwordless/sqlStorage/PasswordlessSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/passwordless/sqlStorage/PasswordlessSQLStorage.java index cfd6dbf7..4722546c 100644 --- a/src/main/java/io/supertokens/pluginInterface/passwordless/sqlStorage/PasswordlessSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/passwordless/sqlStorage/PasswordlessSQLStorage.java @@ -16,13 +16,16 @@ package io.supertokens.pluginInterface.passwordless.sqlStorage; +import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportTransactionRolledBackException; 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.PasswordlessCode; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; +import io.supertokens.pluginInterface.passwordless.PasswordlessImportUser; import io.supertokens.pluginInterface.passwordless.PasswordlessStorage; import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; import io.supertokens.pluginInterface.sqlStorage.SQLStorage; @@ -30,6 +33,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.List; public interface PasswordlessSQLStorage extends PasswordlessStorage, SQLStorage { PasswordlessDevice getDevice_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, @@ -85,4 +89,7 @@ void updateUserPhoneNumber_Transaction(AppIdentifier appIdentifier, TransactionC void deletePasswordlessUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) throws StorageQueryException; + + void importPasswordlessUsers_Transaction(TransactionConnection con, List users) + throws StorageQueryException, TenantOrAppNotFoundException, BulkImportTransactionRolledBackException; } diff --git a/src/main/java/io/supertokens/pluginInterface/sqlStorage/SQLStorage.java b/src/main/java/io/supertokens/pluginInterface/sqlStorage/SQLStorage.java index d1351b30..a66d7801 100644 --- a/src/main/java/io/supertokens/pluginInterface/sqlStorage/SQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/sqlStorage/SQLStorage.java @@ -24,6 +24,8 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import java.sql.SQLException; + public interface SQLStorage extends Storage { T startTransaction(TransactionLogic logic, TransactionIsolationLevel isolationLevel) throws StorageQueryException, StorageTransactionLogicException; @@ -39,10 +41,20 @@ KeyValueInfo getKeyValue_Transaction(TenantIdentifier tenantIdentifier, Transact throws StorageQueryException; interface TransactionLogic { - T mainLogicAndCommit(TransactionConnection con) throws StorageQueryException, StorageTransactionLogicException; + T mainLogicAndCommit(TransactionConnection con) + throws StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException, + SQLException; } public enum TransactionIsolationLevel { SERIALIZABLE, REPEATABLE_READ, READ_COMMITTED, READ_UNCOMMITTED, NONE } + + /* BulkImportProxyStorage methods */ + + void closeConnectionForBulkImportProxyStorage() throws StorageQueryException; + + void commitTransactionForBulkImportProxyStorage() throws StorageQueryException; + + void rollbackTransactionForBulkImportProxyStorage() throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/thirdparty/ThirdPartyImportUser.java b/src/main/java/io/supertokens/pluginInterface/thirdparty/ThirdPartyImportUser.java new file mode 100644 index 00000000..e66784d7 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/thirdparty/ThirdPartyImportUser.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.pluginInterface.thirdparty; + +import io.supertokens.pluginInterface.bulkimport.ImportUserBase; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; + +public class ThirdPartyImportUser extends ImportUserBase { + + public String thirdpartyId; + public String thirdpartyUserId; + + public ThirdPartyImportUser(String email, String userId, String thirdpartyId, String thirdpartyUserId, + TenantIdentifier tenantIdentifier, long timeJoinedInMSSinceEpoch) { + super(userId, email, tenantIdentifier, timeJoinedInMSSinceEpoch); + this.thirdpartyId = thirdpartyId; + this.thirdpartyUserId = thirdpartyUserId; + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/thirdparty/sqlStorage/ThirdPartySQLStorage.java b/src/main/java/io/supertokens/pluginInterface/thirdparty/sqlStorage/ThirdPartySQLStorage.java index ea55a02f..c0f17215 100644 --- a/src/main/java/io/supertokens/pluginInterface/thirdparty/sqlStorage/ThirdPartySQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/thirdparty/sqlStorage/ThirdPartySQLStorage.java @@ -17,11 +17,16 @@ package io.supertokens.pluginInterface.thirdparty.sqlStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.sqlStorage.SQLStorage; import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; +import io.supertokens.pluginInterface.thirdparty.ThirdPartyImportUser; import io.supertokens.pluginInterface.thirdparty.ThirdPartyStorage; +import java.util.List; + public interface ThirdPartySQLStorage extends ThirdPartyStorage, SQLStorage { void updateUserEmail_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String thirdPartyId, @@ -31,4 +36,7 @@ void updateUserEmail_Transaction(AppIdentifier appIdentifier, TransactionConnect void deleteThirdPartyUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) throws StorageQueryException; + + void importThirdPartyUsers_Transaction(TransactionConnection con, List usersToImport) + throws StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException; } diff --git a/src/main/java/io/supertokens/pluginInterface/totp/sqlStorage/TOTPSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/totp/sqlStorage/TOTPSQLStorage.java index ab8cb181..cf9750ca 100644 --- a/src/main/java/io/supertokens/pluginInterface/totp/sqlStorage/TOTPSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/totp/sqlStorage/TOTPSQLStorage.java @@ -13,6 +13,8 @@ import io.supertokens.pluginInterface.totp.exception.UnknownTotpUserIdException; import io.supertokens.pluginInterface.totp.exception.UsedCodeAlreadyExistsException; +import java.util.List; + public interface TOTPSQLStorage extends TOTPStorage, SQLStorage { public int deleteDevice_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, String deviceName) @@ -47,4 +49,7 @@ TOTPDevice getDeviceByName_Transaction(TransactionConnection con, AppIdentifier TOTPDevice createDevice_Transaction(TransactionConnection con, AppIdentifier appIdentifier, TOTPDevice device) throws StorageQueryException, DeviceAlreadyExistsException, TenantOrAppNotFoundException; + + void createDevices_Transaction(TransactionConnection con, AppIdentifier appIdentifier, List devices) + throws StorageQueryException, TenantOrAppNotFoundException; } diff --git a/src/main/java/io/supertokens/pluginInterface/useridmapping/UserIdMappingStorage.java b/src/main/java/io/supertokens/pluginInterface/useridmapping/UserIdMappingStorage.java index eab19ae9..1b7b8c7a 100644 --- a/src/main/java/io/supertokens/pluginInterface/useridmapping/UserIdMappingStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/useridmapping/UserIdMappingStorage.java @@ -25,6 +25,7 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; public interface UserIdMappingStorage extends Storage { @@ -35,6 +36,10 @@ void createUserIdMapping(AppIdentifier appIdentifier, String superTokensUserId, @Nullable String externalUserIdInfo) throws StorageQueryException, UnknownSuperTokensUserIdException, UserIdMappingAlreadyExistsException; + // support method for bulk migration + void createBulkUserIdMapping(AppIdentifier appIdentifier, Map superTokensUserIdToExternalUserId) + throws StorageQueryException; + boolean deleteUserIdMapping(AppIdentifier appIdentifier, String userId, boolean isSuperTokensUserId) throws StorageQueryException; diff --git a/src/main/java/io/supertokens/pluginInterface/useridmapping/sqlStorage/UserIdMappingSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/useridmapping/sqlStorage/UserIdMappingSQLStorage.java index 44bb8573..28e283b4 100644 --- a/src/main/java/io/supertokens/pluginInterface/useridmapping/sqlStorage/UserIdMappingSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/useridmapping/sqlStorage/UserIdMappingSQLStorage.java @@ -23,6 +23,8 @@ import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; +import java.util.List; + public interface UserIdMappingSQLStorage extends UserIdMappingStorage, SQLStorage { UserIdMapping getUserIdMapping_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean isSuperTokensUserId) @@ -30,4 +32,8 @@ UserIdMapping getUserIdMapping_Transaction(TransactionConnection con, AppIdentif UserIdMapping[] getUserIdMapping_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException; + + List getMultipleUserIdMapping_Transaction(TransactionConnection connection, AppIdentifier appIdentifier, + List userIds, boolean isSupertokensIds) + throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/usermetadata/sqlStorage/UserMetadataSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/usermetadata/sqlStorage/UserMetadataSQLStorage.java index dbe5cc74..d391cf3e 100644 --- a/src/main/java/io/supertokens/pluginInterface/usermetadata/sqlStorage/UserMetadataSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/usermetadata/sqlStorage/UserMetadataSQLStorage.java @@ -24,14 +24,24 @@ import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; import io.supertokens.pluginInterface.usermetadata.UserMetadataStorage; +import java.util.List; +import java.util.Map; + public interface UserMetadataSQLStorage extends UserMetadataStorage, SQLStorage { JsonObject getUserMetadata_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId) throws StorageQueryException; + Map getMultipleUsersMetadatas_Transaction(AppIdentifier appIdentifier, TransactionConnection + con, List userIds) + throws StorageQueryException; + int setUserMetadata_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId, JsonObject metadata) throws StorageQueryException, TenantOrAppNotFoundException; + void setMultipleUsersMetadatas_Transaction(AppIdentifier appIdentifier, TransactionConnection con, Map metadataByUserId) + throws StorageQueryException, TenantOrAppNotFoundException; + int deleteUserMetadata_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/userroles/sqlStorage/UserRolesSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/userroles/sqlStorage/UserRolesSQLStorage.java index c395ff31..2d5ab21e 100644 --- a/src/main/java/io/supertokens/pluginInterface/userroles/sqlStorage/UserRolesSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/userroles/sqlStorage/UserRolesSQLStorage.java @@ -25,6 +25,9 @@ import io.supertokens.pluginInterface.userroles.UserRolesStorage; import io.supertokens.pluginInterface.userroles.exception.UnknownRoleException; +import java.util.List; +import java.util.Map; + public interface UserRolesSQLStorage extends UserRolesStorage, SQLStorage { // delete role associated with the input userId from the input roles @@ -54,6 +57,12 @@ int deleteAllPermissionsForRole_Transaction(AppIdentifier appIdentifier, Transac boolean doesRoleExist_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String role) throws StorageQueryException; + List doesMultipleRoleExist_Transaction(AppIdentifier appIdentifier, TransactionConnection con, List roles) + throws StorageQueryException; + void deleteAllRolesForUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException; + + void addRolesToUsers_Transaction(TransactionConnection connection, Map>> rolesToUserByTenants) + throws StorageQueryException; }