diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa28c7f..cb2378be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,56 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [4.0.0] - 2023-09-19 + +- Adds support for account linking +- Adds `AuthRecipeUserInfo` class and removes `UserInfo` from emailpassword, passwordless and thirdparty. +- ActiveUsersStorage interface changes + - Removes `deleteUserActive` + - Adds `deleteUserActive_Transaction` + - Adds `countUsersThatHaveMoreThanOneLoginMethodAndActiveSince` +- EmailPasswordStorage interfaces changes + - Removes `deleteEmailPasswordUser`, `getUserInfoUsingId`, `getUserInfoUsingEmail` + - Changes return type of `signUp` from `UserInfo` to `AuthRecipeUserInfo` + - Changes `PasswordResetTokenInfo` to accept additional param `email` +- EmailPasswordSQLStorage interface changes + - Removes `getUserInfoUsingId_Transaction` + - Adds `deleteEmailPasswordUser_Transaction` +- EmailVerificationStorage interface changes + - Removes `deleteEmailVerificationUserInfo` +- EmailVerificationSQLStorage interface changes + - Adds `deleteEmailVerificationUserInfo_Transaction` +- MultitenancyStorage interface changes + - Removes `addUserIdToTenant` +- MultitenancySQLStorage interface changes + - Adds `addUserIdToTenant_Transaction` +- PasswordlessStorage interface changes + - Changes return type of `createUser` from `UserInfo` to `AuthRecipeUserInfo` + - Removes `deletePasswordlessUser`, `getUserById`, `getUserByEmail`, `getUserByPhoneNumber` +- PasswordlessSQLStorage interface changes + - Adds `deletePasswordlessUser_Transaction` +- SessionInfo accepts additional parameter `recipeUserId` +- SessionSQLStorage interface changes + - Adds `deleteSessionsOfUser_Transaction` +- ThirdPartyStorage interface changes + - Removes `deleteThirdPartyUser`, `getThirdPartyUserInfoUsingId`, `getThirdPartyUserInfoUsingId`, `getThirdPartyUsersByEmail` + - Changes return type of `signUp` from `UserInfo` to `AuthRecipeUserInfo` +- ThirdPartySQLStorage interface changes + - Adds `deleteThirdPartyUser_Transaction` + - Removes `getUserInfoUsingId_Transaction` +- UserIdMappingSQLStorage interface changes + - Adds `getUserIdMapping_Transaction`, `getUserIdMapping_Transaction` +- UserMetadataSQLStorage interface changes + - Adds `deleteUserMetadata_Transaction` +- UserRolesStorage interface changes + - Removes `deleteAllRolesForUser` +- UserRolesSQLStorage interface changes + - Adds `deleteAllRolesForUser_Transaction` + +## [3.0.1] - 2023-07-04 + +- Updates `TenantConfig` toJson function to protect core config as well. + ## [3.0.0] - 2023-06-02 - Adds support for multi-tenancy diff --git a/build.gradle b/build.gradle index 179e4313..62e73f81 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -version = "3.0.0" +version = "4.0.0" repositories { mavenCentral() diff --git a/jar/plugin-interface-3.0.0.jar b/jar/plugin-interface-4.0.0.jar similarity index 53% rename from jar/plugin-interface-3.0.0.jar rename to jar/plugin-interface-4.0.0.jar index 4e092b10..1eedae21 100644 Binary files a/jar/plugin-interface-3.0.0.jar and b/jar/plugin-interface-4.0.0.jar differ diff --git a/src/main/java/io/supertokens/pluginInterface/ActiveUsersStorage.java b/src/main/java/io/supertokens/pluginInterface/ActiveUsersStorage.java index 9516961d..e2b1e7ac 100644 --- a/src/main/java/io/supertokens/pluginInterface/ActiveUsersStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/ActiveUsersStorage.java @@ -3,6 +3,7 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; +import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; public interface ActiveUsersStorage extends NonAuthRecipeStorage { /* Update the last active time of a user to now */ @@ -24,5 +25,8 @@ public interface ActiveUsersStorage extends NonAuthRecipeStorage { int countUsersEnabledMfaAndActiveSince(AppIdentifier appIdentifier, long time) throws StorageQueryException; /* Delete a user from active users table */ - void deleteUserActive(AppIdentifier appIdentifier, String userId) throws StorageQueryException; + void deleteUserActive_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + throws StorageQueryException; + + int countUsersThatHaveMoreThanOneLoginMethodAndActiveSince(AppIdentifier appIdentifier, long sinceTime) throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/RECIPE_ID.java b/src/main/java/io/supertokens/pluginInterface/RECIPE_ID.java index 7bafb724..6aee253f 100644 --- a/src/main/java/io/supertokens/pluginInterface/RECIPE_ID.java +++ b/src/main/java/io/supertokens/pluginInterface/RECIPE_ID.java @@ -22,7 +22,7 @@ public enum RECIPE_ID { EMAIL_PASSWORD("emailpassword"), THIRD_PARTY("thirdparty"), SESSION("session"), EMAIL_VERIFICATION("emailverification"), JWT("jwt"), PASSWORDLESS("passwordless"), USER_METADATA("usermetadata"), USER_ROLES("userroles"), USER_ID_MAPPING("useridmapping"), DASHBOARD("dashboard"), TOTP("totp"), - MULTITENANCY("multitenancy"), MFA("mfa"); + MULTITENANCY("multitenancy"), ACCOUNT_LINKING("accountlinking"), MFA("mfa"); private final String name; diff --git a/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeStorage.java b/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeStorage.java index 0d33e6eb..f554ffab 100644 --- a/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeStorage.java @@ -44,4 +44,24 @@ AuthRecipeUserInfo[] getUsers(TenantIdentifier tenantIdentifier, @Nonnull Intege boolean doesUserIdExist(AppIdentifier appIdentifier, String userId) throws StorageQueryException; boolean doesUserIdExist(TenantIdentifier tenantIdentifierIdentifier, String userId) throws StorageQueryException; + + AuthRecipeUserInfo getPrimaryUserById(AppIdentifier appIdentifier, String userId) throws StorageQueryException; + + String getPrimaryUserIdStrForUserId(AppIdentifier appIdentifier, String userId) throws StorageQueryException; + + AuthRecipeUserInfo[] listPrimaryUsersByEmail(TenantIdentifier tenantIdentifier, String email) + throws StorageQueryException; + + AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(TenantIdentifier tenantIdentifier, String phoneNumber) + throws StorageQueryException; + + AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo(AppIdentifier appIdentifier, String thirdPartyId, String thirdPartyUserId) + throws StorageQueryException; + + AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(TenantIdentifier tenantIdentifier, String thirdPartyId, + String thirdPartyUserId) throws StorageQueryException; + + boolean checkIfUsesAccountLinking(AppIdentifier appIdentifier) throws StorageQueryException; + + int getUsersCountWithMoreThanOneLoginMethod(AppIdentifier appIdentifier) throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeUserInfo.java b/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeUserInfo.java index 29a60aee..7837391f 100644 --- a/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeUserInfo.java +++ b/src/main/java/io/supertokens/pluginInterface/authRecipe/AuthRecipeUserInfo.java @@ -16,22 +16,232 @@ package io.supertokens.pluginInterface.authRecipe; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import io.supertokens.pluginInterface.RECIPE_ID; -public abstract class AuthRecipeUserInfo { +import java.util.*; - public String id; +public class AuthRecipeUserInfo { + + private final String id; + + private String externalUserId = null; + + public boolean isPrimaryUser; + + public LoginMethod[] loginMethods; + + public Set tenantIds; public long timeJoined; - public final String[] tenantIds; + private boolean didCallSetExternalUserId = false; + + public void setExternalUserId(String externalUserId) { + didCallSetExternalUserId = true; + this.externalUserId = externalUserId; + for (LoginMethod loginMethod : this.loginMethods) { + if (loginMethod.getSupertokensUserId().equals(this.id)) { + loginMethod.setExternalUserId(externalUserId); + } + } + } - public AuthRecipeUserInfo(String id, long timeJoined, String[] tenantIds) { + public String getSupertokensOrExternalUserId() { + assert (this.didCallSetExternalUserId); + + if (this.externalUserId != null) { + return this.externalUserId; + } + return this.id; + } + + public String getSupertokensUserId() { + return this.id; + } + + protected AuthRecipeUserInfo(String id, Boolean isPrimaryUser, LoginMethod loginMethods) { + assert (isPrimaryUser != null); this.id = id; - this.timeJoined = timeJoined; - this.tenantIds = tenantIds; + this.isPrimaryUser = isPrimaryUser; + this.loginMethods = new LoginMethod[]{loginMethods}; + this.timeJoined = loginMethods.timeJoined; + this.tenantIds = new HashSet<>(); + this.tenantIds.addAll(loginMethods.tenantIds); + } + + public static AuthRecipeUserInfo create(String id, Boolean isPrimaryUser, LoginMethod loginMethod) { + assert (isPrimaryUser != null); + return new AuthRecipeUserInfo(id, isPrimaryUser, loginMethod); + } + + public void addLoginMethod(LoginMethod loginMethod) { + for (LoginMethod method : this.loginMethods) { + if (method.equals(loginMethod)) { + return; + } + } + LoginMethod[] newLoginMethods = new LoginMethod[this.loginMethods.length + 1]; + System.arraycopy(this.loginMethods, 0, newLoginMethods, 0, this.loginMethods.length); + newLoginMethods[this.loginMethods.length] = loginMethod; + this.loginMethods = Arrays.stream(newLoginMethods).sorted((o1, o2) -> { + if (o1.timeJoined < o2.timeJoined) { + return -1; + } else if (o1.timeJoined > o2.timeJoined) { + return 1; + } + return 0; + }).toArray(LoginMethod[]::new); + if (timeJoined > loginMethod.timeJoined) { + this.timeJoined = loginMethod.timeJoined; + } + + this.tenantIds.addAll(loginMethod.tenantIds); } - public abstract RECIPE_ID getRecipeId(); + @Override + public boolean equals(Object other) { + if (!(other instanceof AuthRecipeUserInfo)) { + return false; + } + AuthRecipeUserInfo otherUser = (AuthRecipeUserInfo) other; + return this.id.equals(otherUser.id) && this.isPrimaryUser == otherUser.isPrimaryUser + && this.timeJoined == otherUser.timeJoined && Arrays.equals(this.loginMethods, otherUser.loginMethods) + && this.tenantIds.equals(otherUser.tenantIds); + } + + @Override + public int hashCode() { + // combine hash codes of all fields + // We multiply with 31 because it's a prime number. + int hashCode = this.id.hashCode(); + hashCode = 31 * hashCode + Boolean.hashCode(this.isPrimaryUser); + hashCode = 31 * hashCode + Long.hashCode(this.timeJoined); + hashCode = 31 * hashCode + Arrays.hashCode(this.loginMethods); + hashCode = 31 * hashCode + this.tenantIds.hashCode(); + return hashCode; + } + public JsonObject toJson() { + if (!didCallSetExternalUserId) { + throw new RuntimeException("Found a bug: Did you forget to call setExternalUserId?"); + } + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("id", getSupertokensOrExternalUserId()); + jsonObject.addProperty("isPrimaryUser", this.isPrimaryUser); + JsonArray tenantIds = new JsonArray(); + for (String tenant : this.tenantIds) { + tenantIds.add(new JsonPrimitive(tenant)); + } + jsonObject.add("tenantIds", tenantIds); + jsonObject.addProperty("timeJoined", this.timeJoined); + + // now we add unique emails, phone numbers and third party across all login methods + Set emails = new HashSet<>(); + Set phoneNumbers = new HashSet<>(); + Set thirdParty = new HashSet<>(); + for (LoginMethod loginMethod : this.loginMethods) { + if (loginMethod.email != null) { + emails.add(loginMethod.email); + } + if (loginMethod.phoneNumber != null) { + phoneNumbers.add(loginMethod.phoneNumber); + } + if (loginMethod.thirdParty != null) { + thirdParty.add(loginMethod.thirdParty); + } + } + JsonArray emailsJson = new JsonArray(); + for (String email : emails) { + emailsJson.add(new JsonPrimitive(email)); + } + jsonObject.add("emails", emailsJson); + JsonArray phoneNumbersJson = new JsonArray(); + for (String phoneNumber : phoneNumbers) { + phoneNumbersJson.add(new JsonPrimitive(phoneNumber)); + } + jsonObject.add("phoneNumbers", phoneNumbersJson); + JsonArray thirdPartyJson = new JsonArray(); + for (LoginMethod.ThirdParty tpInfo : thirdParty) { + JsonObject j = new JsonObject(); + j.addProperty("id", tpInfo.id); + j.addProperty("userId", tpInfo.userId); + thirdPartyJson.add(j); + } + jsonObject.add("thirdParty", thirdPartyJson); + + // now we add login methods.. + JsonArray loginMethodsArr = new JsonArray(); + for (LoginMethod lM : this.loginMethods) { + JsonObject lMJsonObject = new JsonObject(); + JsonArray lMTenantIds = new JsonArray(); + for (String tenant : lM.tenantIds) { + lMTenantIds.add(new JsonPrimitive(tenant)); + } + lMJsonObject.add("tenantIds", lMTenantIds); + lMJsonObject.addProperty("recipeUserId", lM.getSupertokensOrExternalUserId()); + lMJsonObject.addProperty("verified", lM.verified); + lMJsonObject.addProperty("timeJoined", lM.timeJoined); + lMJsonObject.addProperty("recipeId", lM.recipeId.toString()); + if (lM.email != null) { + lMJsonObject.addProperty("email", lM.email); + } + if (lM.phoneNumber != null) { + lMJsonObject.addProperty("phoneNumber", lM.phoneNumber); + } + if (lM.thirdParty != null) { + JsonObject thirdPartyJsonObject = new JsonObject(); + thirdPartyJsonObject.addProperty("id", lM.thirdParty.id); + thirdPartyJsonObject.addProperty("userId", lM.thirdParty.userId); + lMJsonObject.add("thirdParty", thirdPartyJsonObject); + } + loginMethodsArr.add(lMJsonObject); + } + jsonObject.add("loginMethods", loginMethodsArr); + return jsonObject; + } + + public JsonObject toJsonWithoutAccountLinking() { + if (!didCallSetExternalUserId) { + throw new RuntimeException("Found a bug: Did you forget to call setExternalUserId?"); + } + // this is for older CDI versions. + if (this.loginMethods.length != 1) { + throw new IllegalStateException( + "Please use a CDI version that is greater than the one in which account linking feature was " + + "enabled."); + } + LoginMethod loginMethod = loginMethods[0]; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("id", loginMethod.getSupertokensOrExternalUserId()); + jsonObject.addProperty("timeJoined", loginMethod.timeJoined); + JsonArray tenantIds = new JsonArray(); + for (String tenant : loginMethod.tenantIds) { + tenantIds.add(new JsonPrimitive(tenant)); + } + jsonObject.add("tenantIds", tenantIds); + if (loginMethod.recipeId == RECIPE_ID.EMAIL_PASSWORD) { + jsonObject.addProperty("email", loginMethod.email); + } else if (loginMethod.recipeId == RECIPE_ID.THIRD_PARTY) { + jsonObject.addProperty("email", loginMethod.email); + JsonObject thirdPartyJson = new JsonObject(); + assert loginMethod.thirdParty != null; + thirdPartyJson.addProperty("id", loginMethod.thirdParty.id); + thirdPartyJson.addProperty("userId", loginMethod.thirdParty.userId); + jsonObject.add("thirdParty", thirdPartyJson); + } else if (loginMethod.recipeId == RECIPE_ID.PASSWORDLESS) { + if (loginMethod.email != null) { + jsonObject.addProperty("email", loginMethod.email); + } + if (loginMethod.phoneNumber != null) { + jsonObject.addProperty("phoneNumber", loginMethod.phoneNumber); + } + } else { + throw new UnsupportedOperationException("Please search for bugs"); + } + + return jsonObject; + } } diff --git a/src/main/java/io/supertokens/pluginInterface/authRecipe/LoginMethod.java b/src/main/java/io/supertokens/pluginInterface/authRecipe/LoginMethod.java new file mode 100644 index 00000000..c3cc678c --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/authRecipe/LoginMethod.java @@ -0,0 +1,181 @@ +/* + * 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.pluginInterface.authRecipe; + +import io.supertokens.pluginInterface.RECIPE_ID; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class LoginMethod { + + public boolean verified; + + public final long timeJoined; + + private final String recipeUserId; + + private String externalUserId; + + public final RECIPE_ID recipeId; + + public final String email; + + public final String phoneNumber; + + public final ThirdParty thirdParty; + + public final Set tenantIds; + + public transient final String passwordHash; + + private boolean didCallSetExternalUserId = false; + + public LoginMethod(String recipeUserId, long timeJoined, boolean verified, String email, + String passwordHash, String[] tenantIds) { + this.verified = verified; + this.timeJoined = timeJoined; + this.recipeUserId = recipeUserId; + this.recipeId = RECIPE_ID.EMAIL_PASSWORD; + this.email = email; + this.phoneNumber = null; + this.thirdParty = null; + this.tenantIds = new HashSet<>(); + Collections.addAll(this.tenantIds, tenantIds); + this.passwordHash = passwordHash; + } + + public LoginMethod(String recipeUserId, long timeJoined, boolean verified, PasswordlessInfo passwordlessInfo, + String[] tenantIds) { + this.verified = verified; + this.timeJoined = timeJoined; + this.recipeUserId = recipeUserId; + this.recipeId = RECIPE_ID.PASSWORDLESS; + this.email = passwordlessInfo.email; + this.phoneNumber = passwordlessInfo.phoneNumber; + this.tenantIds = new HashSet<>(); + Collections.addAll(this.tenantIds, tenantIds); + this.thirdParty = null; + this.passwordHash = null; + } + + public LoginMethod(String recipeUserId, long timeJoined, boolean verified, String email, ThirdParty thirdPartyInfo, + String[] tenantIds) { + this.verified = verified; + this.timeJoined = timeJoined; + this.recipeUserId = recipeUserId; + this.recipeId = RECIPE_ID.THIRD_PARTY; + this.email = email; + this.tenantIds = new HashSet<>(); + Collections.addAll(this.tenantIds, tenantIds); + this.thirdParty = thirdPartyInfo; + this.phoneNumber = null; + this.passwordHash = null; + } + + public void setExternalUserId(String externalUserId) { + didCallSetExternalUserId = true; + this.externalUserId = externalUserId; + } + + public void setVerified() { + this.verified = true; + } + + public String getSupertokensOrExternalUserId() { + assert (this.didCallSetExternalUserId); + if (this.externalUserId != null) { + return this.externalUserId; + } + return this.recipeUserId; + } + + // This function should never be called in the API layer unless there is a strong reason to do so + public String getSupertokensUserId() { + return this.recipeUserId; + } + + public static class PasswordlessInfo { + String email; + String phoneNumber; + + public PasswordlessInfo(String email, String phoneNumber) { + if (email == null && phoneNumber == null) { + throw new IllegalArgumentException("Both email and phoneNumber cannot be null"); + } + this.email = email; + this.phoneNumber = phoneNumber; + } + } + + public static class ThirdParty { + public String id; + public String userId; + + public ThirdParty(String id, String userId) { + this.id = id; + this.userId = userId; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ThirdParty)) { + return false; + } + ThirdParty otherThirdParty = (ThirdParty) other; + return this.id.equals(otherThirdParty.id) && this.userId.equals(otherThirdParty.userId); + } + + @Override + public int hashCode() { + return (id + "|" + userId).hashCode(); + } + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof LoginMethod)) { + return false; + } + LoginMethod otherLoginMethod = (LoginMethod) other; + return this.verified == otherLoginMethod.verified && this.timeJoined == otherLoginMethod.timeJoined + && this.recipeUserId.equals(otherLoginMethod.recipeUserId) && this.recipeId == otherLoginMethod.recipeId + && java.util.Objects.equals(this.email, otherLoginMethod.email) + && java.util.Objects.equals(this.phoneNumber, otherLoginMethod.phoneNumber) + && java.util.Objects.equals(this.passwordHash, otherLoginMethod.passwordHash) + && java.util.Objects.equals(this.thirdParty, otherLoginMethod.thirdParty) + && this.tenantIds.equals(otherLoginMethod.tenantIds); + } + + @Override + public int hashCode() { + // combine hash codes of all fields + // We multiply with 31 because it's a prime number. + int result = 1; + result = 31 * result + (verified ? 1 : 0); + result = 31 * result + (int) (timeJoined ^ (timeJoined >>> 32)); + result = 31 * result + recipeUserId.hashCode(); + result = 31 * result + recipeId.hashCode(); + result = 31 * result + (email != null ? email.hashCode() : 0); + result = 31 * result + (phoneNumber != null ? phoneNumber.hashCode() : 0); + result = 31 * result + tenantIds.hashCode(); + result = 31 * result + (passwordHash != null ? passwordHash.hashCode() : 0); + result = 31 * result + (thirdParty != null ? thirdParty.hashCode() : 0); + return result; + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/authRecipe/sqlStorage/AuthRecipeSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/authRecipe/sqlStorage/AuthRecipeSQLStorage.java new file mode 100644 index 00000000..eacc4aa2 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/authRecipe/sqlStorage/AuthRecipeSQLStorage.java @@ -0,0 +1,63 @@ +/* + * 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.pluginInterface.authRecipe.sqlStorage; + +import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.sqlStorage.SQLStorage; +import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; + +public interface AuthRecipeSQLStorage extends AuthRecipeStorage, SQLStorage { + + AuthRecipeUserInfo getPrimaryUserById_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + String userId) + throws StorageQueryException; + + // lock order: + // - emailpassword table + // - thirdparty table + // - passwordless table + AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(AppIdentifier appIdentifier, + TransactionConnection con, + String email) + throws StorageQueryException; + + // locks only passwordless table + AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentifier appIdentifier, + TransactionConnection con, String phoneNumber) + throws StorageQueryException; + + // locks on thirdparty table + AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_Transaction(AppIdentifier appIdentifier, + TransactionConnection con, String thirdPartyId, + String thirdPartyUserId) + throws StorageQueryException; + + void makePrimaryUser_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId) + throws StorageQueryException; + + void linkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String recipeUserId, + String primaryUserId) throws StorageQueryException; + + void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String primaryUserId, String recipeUserId) + throws StorageQueryException; + + boolean doesUserIdExist_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String externalUserId) throws StorageQueryException; +} diff --git a/src/main/java/io/supertokens/pluginInterface/dashboard/DashboardSearchTags.java b/src/main/java/io/supertokens/pluginInterface/dashboard/DashboardSearchTags.java index 833b1be8..5f6b9b01 100644 --- a/src/main/java/io/supertokens/pluginInterface/dashboard/DashboardSearchTags.java +++ b/src/main/java/io/supertokens/pluginInterface/dashboard/DashboardSearchTags.java @@ -1,17 +1,18 @@ package io.supertokens.pluginInterface.dashboard; import java.util.ArrayList; +import java.util.List; import javax.annotation.Nullable; public class DashboardSearchTags { - public ArrayList emails; - public ArrayList phoneNumbers; - public ArrayList providers; + public List emails; + public List phoneNumbers; + public List providers; - public DashboardSearchTags(@Nullable ArrayList emails, @Nullable ArrayList phones, - @Nullable ArrayList providers) { + public DashboardSearchTags(@Nullable List emails, @Nullable List phones, + @Nullable List providers) { this.emails = emails; this.phoneNumbers = phones; this.providers = providers; @@ -19,14 +20,14 @@ public DashboardSearchTags(@Nullable ArrayList emails, @Nullable ArrayLi public boolean shouldEmailPasswordTableBeSearched() { - ArrayList nonNullSearchTags = getNonNullSearchFields(); + List nonNullSearchTags = getNonNullSearchFields(); return nonNullSearchTags.contains(SUPPORTED_SEARCH_TAGS.EMAIL) && nonNullSearchTags.size() == 1; } public boolean shouldThirdPartyTableBeSearched() { - ArrayList nonNullSearchTags = getNonNullSearchFields(); + List nonNullSearchTags = getNonNullSearchFields(); if(nonNullSearchTags.contains(SUPPORTED_SEARCH_TAGS.EMAIL) && nonNullSearchTags.contains(SUPPORTED_SEARCH_TAGS.PROVIDER)){ return nonNullSearchTags.size() == 2; } @@ -39,7 +40,7 @@ public boolean shouldThirdPartyTableBeSearched() { } public boolean shouldPasswordlessTableBeSearched() { - ArrayList nonNullSearchTags = getNonNullSearchFields(); + List nonNullSearchTags = getNonNullSearchFields(); if(nonNullSearchTags.contains(SUPPORTED_SEARCH_TAGS.EMAIL) && nonNullSearchTags.contains(SUPPORTED_SEARCH_TAGS.PHONE)){ return nonNullSearchTags.size() == 2; } @@ -51,8 +52,8 @@ public boolean shouldPasswordlessTableBeSearched() { return false; } - private ArrayList getNonNullSearchFields() { - ArrayList nonNullSearchTags = new ArrayList<>(); + private List getNonNullSearchFields() { + List nonNullSearchTags = new ArrayList<>(); if (this.emails != null) { nonNullSearchTags.add(SUPPORTED_SEARCH_TAGS.EMAIL); diff --git a/src/main/java/io/supertokens/pluginInterface/emailpassword/EmailPasswordStorage.java b/src/main/java/io/supertokens/pluginInterface/emailpassword/EmailPasswordStorage.java index 13e789aa..fd35735c 100644 --- a/src/main/java/io/supertokens/pluginInterface/emailpassword/EmailPasswordStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/emailpassword/EmailPasswordStorage.java @@ -17,6 +17,7 @@ package io.supertokens.pluginInterface.emailpassword; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicatePasswordResetTokenException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; @@ -29,19 +30,10 @@ public interface EmailPasswordStorage extends AuthRecipeStorage { // we pass tenantIdentifier here cause this also adds to the userId <-> tenantId mapping - UserInfo signUp(TenantIdentifier tenantIdentifier, String id, String email, String passwordHash, long timeJoined) + AuthRecipeUserInfo signUp(TenantIdentifier tenantIdentifier, String id, String email, String passwordHash, long timeJoined) throws StorageQueryException, DuplicateUserIdException, DuplicateEmailException, TenantOrAppNotFoundException; - // this deletion of a user is app wide since the same user ID can be shared across tenants - void deleteEmailPasswordUser(AppIdentifier appIdentifier, String userId) throws StorageQueryException; - - UserInfo getUserInfoUsingId(AppIdentifier appIdentifier, String id) throws StorageQueryException; - - // Here we pass in TenantIdentifier cause the same email can be shared across tenants, but yield different - // user IDs - UserInfo getUserInfoUsingEmail(TenantIdentifier tenantIdentifier, String email) throws StorageQueryException; - // password reset stuff is app wide cause changing the password for a user affects all the tenants // across which it's shared. void addPasswordResetToken(AppIdentifier appIdentifier, PasswordResetTokenInfo passwordResetTokenInfo) diff --git a/src/main/java/io/supertokens/pluginInterface/emailpassword/PasswordResetTokenInfo.java b/src/main/java/io/supertokens/pluginInterface/emailpassword/PasswordResetTokenInfo.java index 0d3e146d..be506838 100644 --- a/src/main/java/io/supertokens/pluginInterface/emailpassword/PasswordResetTokenInfo.java +++ b/src/main/java/io/supertokens/pluginInterface/emailpassword/PasswordResetTokenInfo.java @@ -24,9 +24,12 @@ public class PasswordResetTokenInfo { public final long tokenExpiry; - public PasswordResetTokenInfo(String userId, String token, long tokenExpiry) { + public final String email; + + public PasswordResetTokenInfo(String userId, String token, long tokenExpiry, String email) { this.userId = userId; this.token = token; this.tokenExpiry = tokenExpiry; + this.email = email; } } diff --git a/src/main/java/io/supertokens/pluginInterface/emailpassword/UserInfo.java b/src/main/java/io/supertokens/pluginInterface/emailpassword/UserInfo.java deleted file mode 100644 index bc65964a..00000000 --- a/src/main/java/io/supertokens/pluginInterface/emailpassword/UserInfo.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.pluginInterface.emailpassword; - -import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; - -public class UserInfo extends AuthRecipeUserInfo { - - public final String email; - - // using transient, we tell Gson not to include this when creating a JSON - public transient final String passwordHash; - - public UserInfo(String id, String email, String passwordHash, long timeJoined, String[] tenantIds) { - super(id, timeJoined, tenantIds); - this.email = email; - this.passwordHash = passwordHash; - } - - @Override - public RECIPE_ID getRecipeId() { - return RECIPE_ID.EMAIL_PASSWORD; - } - - @Override - public boolean equals(Object other) { - if (other instanceof UserInfo) { - UserInfo otherUserInfo = (UserInfo) other; - return otherUserInfo.email.equals(this.email) && otherUserInfo.passwordHash.equals(this.passwordHash) - && otherUserInfo.id.equals(this.id) && otherUserInfo.timeJoined == this.timeJoined; - } - return false; - } -} 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 78b5ea68..af47334f 100644 --- a/src/main/java/io/supertokens/pluginInterface/emailpassword/sqlStorage/EmailPasswordSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/emailpassword/sqlStorage/EmailPasswordSQLStorage.java @@ -18,7 +18,6 @@ import io.supertokens.pluginInterface.emailpassword.EmailPasswordStorage; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -46,6 +45,8 @@ void updateUsersEmail_Transaction(AppIdentifier appIdentifier, TransactionConnec String email) throws StorageQueryException, DuplicateEmailException; - UserInfo getUserInfoUsingId_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId) + // this deletion of a user is app wide since the same user ID can be shared across tenants + void deleteEmailPasswordUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, + boolean deleteUserIdMappingToo) throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/emailverification/EmailVerificationStorage.java b/src/main/java/io/supertokens/pluginInterface/emailverification/EmailVerificationStorage.java index 1d436ef5..aefd02c5 100644 --- a/src/main/java/io/supertokens/pluginInterface/emailverification/EmailVerificationStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/emailverification/EmailVerificationStorage.java @@ -31,9 +31,8 @@ void addEmailVerificationToken(TenantIdentifier tenantIdentifier, EmailVerificat EmailVerificationTokenInfo getEmailVerificationTokenInfo(TenantIdentifier tenantIdentifier, String token) throws StorageQueryException; - void deleteEmailVerificationUserInfo(AppIdentifier appIdentifier, String userId) throws StorageQueryException; - - boolean deleteEmailVerificationUserInfo(TenantIdentifier tenantIdentifier, String userId) throws StorageQueryException; + boolean deleteEmailVerificationUserInfo(TenantIdentifier tenantIdentifier, String userId) + throws StorageQueryException; void revokeAllTokens(TenantIdentifier tenantIdentifier, String userId, String email) throws StorageQueryException; @@ -41,7 +40,8 @@ EmailVerificationTokenInfo getEmailVerificationTokenInfo(TenantIdentifier tenant void deleteExpiredEmailVerificationTokens() throws StorageQueryException; - EmailVerificationTokenInfo[] getAllEmailVerificationTokenInfoForUser(TenantIdentifier tenantIdentifier, String userId, + EmailVerificationTokenInfo[] getAllEmailVerificationTokenInfoForUser(TenantIdentifier tenantIdentifier, + String userId, String email) throws StorageQueryException; 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 14ceb117..877fb902 100644 --- a/src/main/java/io/supertokens/pluginInterface/emailverification/sqlStorage/EmailVerificationSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/emailverification/sqlStorage/EmailVerificationSQLStorage.java @@ -32,7 +32,8 @@ EmailVerificationTokenInfo[] getAllEmailVerificationTokenInfoForUser_Transaction String userId, String email) throws StorageQueryException; - void deleteAllEmailVerificationTokensForUser_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, + void deleteAllEmailVerificationTokensForUser_Transaction(TenantIdentifier tenantIdentifier, + TransactionConnection con, String userId, String email) throws StorageQueryException; @@ -41,4 +42,7 @@ void updateIsEmailVerified_Transaction(AppIdentifier appIdentifier, TransactionC 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/multitenancy/MultitenancyStorage.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/MultitenancyStorage.java index 5b3e05ce..d8363133 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/MultitenancyStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/MultitenancyStorage.java @@ -52,10 +52,6 @@ void overwriteTenantConfig(TenantConfig config) throws TenantOrAppNotFoundExcept TenantConfig[] getAllTenants() throws StorageQueryException; - boolean addUserIdToTenant(TenantIdentifier tenantIdentifier, String userId) throws TenantOrAppNotFoundException, - UnknownUserIdException, StorageQueryException, DuplicateEmailException, DuplicateThirdPartyUserException, - DuplicatePhoneNumberException; - boolean removeUserIdFromTenant(TenantIdentifier tenantIdentifier, String userId) throws StorageQueryException; } \ No newline at end of file diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java index d5b00481..db90e86a 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java @@ -89,7 +89,7 @@ public int hashCode() { return tenantIdentifier.hashCode(); } - public JsonObject toJson(boolean shouldProtectDbConfig, Storage storage) { + public JsonObject toJson(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { Gson gson = new Gson(); JsonObject tenantConfigObject = gson.toJsonTree(this).getAsJsonObject(); tenantConfigObject.addProperty("tenantId", this.tenantIdentifier.getTenantId()); @@ -101,6 +101,12 @@ public JsonObject toJson(boolean shouldProtectDbConfig, Storage storage) { tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config); } } + + for (String config : protectedCoreConfigs) { + if (tenantConfigObject.get("coreConfig").getAsJsonObject().has(config)) { + tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config); + } + } } return tenantConfigObject; diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/sqlStorage/MultitenancySQLStorage.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/sqlStorage/MultitenancySQLStorage.java new file mode 100644 index 00000000..114fad86 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/sqlStorage/MultitenancySQLStorage.java @@ -0,0 +1,34 @@ +/* + * 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.pluginInterface.multitenancy.sqlStorage; + +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.MultitenancyStorage; +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; + +public interface MultitenancySQLStorage extends MultitenancyStorage, SQLStorage { + boolean addUserIdToTenant_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, String userId) + throws StorageQueryException, TenantOrAppNotFoundException, DuplicateEmailException, + DuplicateThirdPartyUserException, DuplicatePhoneNumberException, UnknownUserIdException; +} diff --git a/src/main/java/io/supertokens/pluginInterface/passwordless/PasswordlessStorage.java b/src/main/java/io/supertokens/pluginInterface/passwordless/PasswordlessStorage.java index e9e6eae1..0d64f80a 100644 --- a/src/main/java/io/supertokens/pluginInterface/passwordless/PasswordlessStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/passwordless/PasswordlessStorage.java @@ -17,10 +17,10 @@ package io.supertokens.pluginInterface.passwordless; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; 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.*; @@ -37,12 +37,11 @@ void createDeviceWithCode(TenantIdentifier tenantIdentifier, @Nullable String em void createCode(TenantIdentifier tenantIdentifier, PasswordlessCode code) throws StorageQueryException, UnknownDeviceIdHash, DuplicateCodeIdException, DuplicateLinkCodeHashException; - UserInfo createUser(TenantIdentifier tenantIdentifier, String id, @Nullable String email, @Nullable String phoneNumber, long timeJoined) + AuthRecipeUserInfo createUser(TenantIdentifier tenantIdentifier, String id, @Nullable String email, + @Nullable String phoneNumber, long timeJoined) throws StorageQueryException, DuplicateEmailException, DuplicatePhoneNumberException, DuplicateUserIdException, TenantOrAppNotFoundException; - void deletePasswordlessUser(AppIdentifier appIdentifier, String userId) throws StorageQueryException; - PasswordlessDevice getDevice(TenantIdentifier tenantIdentifier, String deviceIdHash) throws StorageQueryException; PasswordlessDevice[] getDevicesByEmail(TenantIdentifier tenantIdentifier, @Nonnull String email) @@ -60,11 +59,4 @@ PasswordlessCode[] getCodesOfDevice(TenantIdentifier tenantIdentifier, String de PasswordlessCode getCodeByLinkCodeHash(TenantIdentifier tenantIdentifier, String linkCode) throws StorageQueryException; - - UserInfo getUserById(AppIdentifier appIdentifier, String userId) throws StorageQueryException; - - UserInfo getUserByEmail(TenantIdentifier tenantIdentifier, @Nonnull String email) throws StorageQueryException; - - UserInfo getUserByPhoneNumber(TenantIdentifier tenantIdentifier, @Nonnull String phoneNumber) - throws StorageQueryException; } \ No newline at end of file diff --git a/src/main/java/io/supertokens/pluginInterface/passwordless/UserInfo.java b/src/main/java/io/supertokens/pluginInterface/passwordless/UserInfo.java deleted file mode 100644 index 9e8579b6..00000000 --- a/src/main/java/io/supertokens/pluginInterface/passwordless/UserInfo.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.pluginInterface.passwordless; - -import javax.annotation.Nullable; - -import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; - -public class UserInfo extends AuthRecipeUserInfo { - public final String email; - public final String phoneNumber; - - public UserInfo(String id, @Nullable String email, @Nullable String phoneNumber, long timeJoined, String[] tenantIds) { - super(id, timeJoined, tenantIds); - - if (email == null && phoneNumber == null) { - throw new IllegalArgumentException("Both email and phoneNumber cannot be null"); - } - - this.email = email; - this.phoneNumber = phoneNumber; - } - - @Override - public RECIPE_ID getRecipeId() { - return RECIPE_ID.PASSWORDLESS; - } - - @Override - public boolean equals(Object other) { - if (other instanceof UserInfo) { - UserInfo otherUserInfo = (UserInfo) other; - return ((otherUserInfo.email == null && this.email == null) || otherUserInfo.email.equals(this.email)) - && ((otherUserInfo.phoneNumber == null && this.phoneNumber == null) - || otherUserInfo.phoneNumber.equals(this.phoneNumber)) - && otherUserInfo.id.equals(this.id) && otherUserInfo.timeJoined == this.timeJoined; - } - return false; - } -} 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 ef47754f..cfd6dbf7 100644 --- a/src/main/java/io/supertokens/pluginInterface/passwordless/sqlStorage/PasswordlessSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/passwordless/sqlStorage/PasswordlessSQLStorage.java @@ -81,4 +81,8 @@ void updateUserPhoneNumber_Transaction(AppIdentifier appIdentifier, TransactionC @Nonnull String userId, @Nullable String phoneNumber) throws StorageQueryException, UnknownUserIdException, DuplicatePhoneNumberException; + + void deletePasswordlessUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, + boolean deleteUserIdMappingToo) + throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/session/SessionInfo.java b/src/main/java/io/supertokens/pluginInterface/session/SessionInfo.java index 50b670e8..48a206bf 100644 --- a/src/main/java/io/supertokens/pluginInterface/session/SessionInfo.java +++ b/src/main/java/io/supertokens/pluginInterface/session/SessionInfo.java @@ -22,16 +22,19 @@ public class SessionInfo { transient public String refreshTokenHash2; public String sessionHandle; public String userId; + public String recipeUserId; public JsonObject userDataInDatabase; public long expiry; public JsonObject userDataInJWT; public long timeCreated; public transient boolean useStaticKey; - public SessionInfo(String sessionHandle, String userId, String refreshTokenHash2, JsonObject userDataInDatabase, - long expiry, JsonObject userDataInJWT, long timeCreated, boolean useStaticKey) { + public SessionInfo(String sessionHandle, String userId, String recipeUserId, String refreshTokenHash2, + JsonObject userDataInDatabase, + long expiry, JsonObject userDataInJWT, long timeCreated, boolean useStaticKey) { this.sessionHandle = sessionHandle; - this.userId = userId; + this.userId = userId == null ? recipeUserId : userId; + this.recipeUserId = recipeUserId; this.refreshTokenHash2 = refreshTokenHash2; this.userDataInDatabase = userDataInDatabase; this.expiry = expiry; diff --git a/src/main/java/io/supertokens/pluginInterface/session/noSqlStorage/SessionInfoWithLastUpdated.java b/src/main/java/io/supertokens/pluginInterface/session/noSqlStorage/SessionInfoWithLastUpdated.java index d96be4d5..c059aa32 100644 --- a/src/main/java/io/supertokens/pluginInterface/session/noSqlStorage/SessionInfoWithLastUpdated.java +++ b/src/main/java/io/supertokens/pluginInterface/session/noSqlStorage/SessionInfoWithLastUpdated.java @@ -23,9 +23,11 @@ public class SessionInfoWithLastUpdated extends SessionInfo { public String lastUpdatedSign; public SessionInfoWithLastUpdated(String sessionHandle, String userId, String refreshTokenHash2, - JsonObject userDataInDatabase, long expiry, JsonObject userDataInJWT, long timeCreated, boolean useStaticKey, - String lastUpdatedSign) { - super(sessionHandle, userId, refreshTokenHash2, userDataInDatabase, expiry, userDataInJWT, timeCreated, useStaticKey); + JsonObject userDataInDatabase, long expiry, JsonObject userDataInJWT, + long timeCreated, boolean useStaticKey, + String lastUpdatedSign) { + super(sessionHandle, userId, userId, refreshTokenHash2, userDataInDatabase, expiry, userDataInJWT, timeCreated, + useStaticKey); this.lastUpdatedSign = lastUpdatedSign; } } diff --git a/src/main/java/io/supertokens/pluginInterface/session/sqlStorage/SessionSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/session/sqlStorage/SessionSQLStorage.java index c84bc3a2..d4556199 100644 --- a/src/main/java/io/supertokens/pluginInterface/session/sqlStorage/SessionSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/session/sqlStorage/SessionSQLStorage.java @@ -55,4 +55,7 @@ SessionInfo getSessionInfo_Transaction(TenantIdentifier tenantIdentifier, Transa void updateSessionInfo_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, String sessionHandle, String refreshTokenHash2, long expiry) throws StorageQueryException; + + void deleteSessionsOfUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/thirdparty/ThirdPartyStorage.java b/src/main/java/io/supertokens/pluginInterface/thirdparty/ThirdPartyStorage.java index 22f51256..71d28434 100644 --- a/src/main/java/io/supertokens/pluginInterface/thirdparty/ThirdPartyStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/thirdparty/ThirdPartyStorage.java @@ -17,28 +17,18 @@ package io.supertokens.pluginInterface.thirdparty; 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.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateUserIdException; -import javax.annotation.Nonnull; - public interface ThirdPartyStorage extends AuthRecipeStorage { - UserInfo signUp(TenantIdentifier tenantIdentifier, String id, String email, UserInfo.ThirdParty thirdParty, long timeJoined) + AuthRecipeUserInfo signUp(TenantIdentifier tenantIdentifier, String id, String email, LoginMethod.ThirdParty thirdParty, + long timeJoined) throws StorageQueryException, DuplicateUserIdException, DuplicateThirdPartyUserException, TenantOrAppNotFoundException; - - void deleteThirdPartyUser(AppIdentifier appIdentifier, String userId) throws StorageQueryException; - - UserInfo getThirdPartyUserInfoUsingId(TenantIdentifier tenantIdentifier, String thirdPartyId, - String thirdPartyUserId) throws StorageQueryException; - - UserInfo getThirdPartyUserInfoUsingId(AppIdentifier appIdentifier, String userId) throws StorageQueryException; - - UserInfo[] getThirdPartyUsersByEmail(TenantIdentifier tenantIdentifier, @Nonnull String email) - throws StorageQueryException; } \ No newline at end of file diff --git a/src/main/java/io/supertokens/pluginInterface/thirdparty/UserInfo.java b/src/main/java/io/supertokens/pluginInterface/thirdparty/UserInfo.java deleted file mode 100644 index ef85520e..00000000 --- a/src/main/java/io/supertokens/pluginInterface/thirdparty/UserInfo.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.pluginInterface.thirdparty; - -import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; - -public class UserInfo extends AuthRecipeUserInfo { - - public final ThirdParty thirdParty; - - public final String email; - - public UserInfo(String id, String email, ThirdParty thirdParty, long timeJoined, String[] tenantIds) { - super(id, timeJoined, tenantIds); - this.thirdParty = thirdParty; - this.email = email; - } - - public static class ThirdParty { - public final String id; - - public final String userId; - - public ThirdParty(String id, String userId) { - this.id = id; - this.userId = userId; - } - } - - @Override - public RECIPE_ID getRecipeId() { - return RECIPE_ID.THIRD_PARTY; - } - - @Override - public boolean equals(Object other) { - if (other instanceof UserInfo) { - UserInfo otherUserInfo = (UserInfo) other; - return otherUserInfo.email.equals(this.email) && otherUserInfo.id.equals(this.id) - && otherUserInfo.timeJoined == this.timeJoined - && otherUserInfo.thirdParty.userId.equals(this.thirdParty.userId) - && otherUserInfo.thirdParty.id.equals(this.thirdParty.id); - } - return false; - } -} 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 08260679..ea55a02f 100644 --- a/src/main/java/io/supertokens/pluginInterface/thirdparty/sqlStorage/ThirdPartySQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/thirdparty/sqlStorage/ThirdPartySQLStorage.java @@ -21,15 +21,14 @@ import io.supertokens.pluginInterface.sqlStorage.SQLStorage; import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; import io.supertokens.pluginInterface.thirdparty.ThirdPartyStorage; -import io.supertokens.pluginInterface.thirdparty.UserInfo; public interface ThirdPartySQLStorage extends ThirdPartyStorage, SQLStorage { - UserInfo getUserInfoUsingId_Transaction(AppIdentifier appIdentifier, TransactionConnection con, - String thirdPartyId, String thirdPartyUserId) - throws StorageQueryException; - void updateUserEmail_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String thirdPartyId, String thirdPartyUserId, String newEmail) throws StorageQueryException; + + void deleteThirdPartyUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, + boolean deleteUserIdMappingToo) + 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 new file mode 100644 index 00000000..4d8ab903 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/useridmapping/sqlStorage/UserIdMappingSQLStorage.java @@ -0,0 +1,33 @@ +/* + * 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.pluginInterface.useridmapping.sqlStorage; + +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.sqlStorage.SQLStorage; +import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; +import io.supertokens.pluginInterface.useridmapping.UserIdMapping; +import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; + +public interface UserIdMappingSQLStorage extends UserIdMappingStorage, SQLStorage { + UserIdMapping getUserIdMapping_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean isSuperTokensUserId) + throws StorageQueryException; + + UserIdMapping[] getUserIdMapping_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + 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 99d5aaf2..dbe5cc74 100644 --- a/src/main/java/io/supertokens/pluginInterface/usermetadata/sqlStorage/UserMetadataSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/usermetadata/sqlStorage/UserMetadataSQLStorage.java @@ -31,4 +31,7 @@ JsonObject getUserMetadata_Transaction(AppIdentifier appIdentifier, TransactionC int setUserMetadata_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId, JsonObject metadata) throws StorageQueryException, TenantOrAppNotFoundException; + + int deleteUserMetadata_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/userroles/UserRolesStorage.java b/src/main/java/io/supertokens/pluginInterface/userroles/UserRolesStorage.java index e080df6f..0f26d768 100644 --- a/src/main/java/io/supertokens/pluginInterface/userroles/UserRolesStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/userroles/UserRolesStorage.java @@ -54,6 +54,4 @@ void addRoleToUser(TenantIdentifier tenantIdentifier, String userId, String role // delete all roles for the input userId int deleteAllRolesForUser(TenantIdentifier tenantIdentifier, String userId) throws StorageQueryException; - - void deleteAllRolesForUser(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 a887ee33..c395ff31 100644 --- a/src/main/java/io/supertokens/pluginInterface/userroles/sqlStorage/UserRolesSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/userroles/sqlStorage/UserRolesSQLStorage.java @@ -53,4 +53,7 @@ int deleteAllPermissionsForRole_Transaction(AppIdentifier appIdentifier, Transac // check if a role exists boolean doesRoleExist_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String role) throws StorageQueryException; + + void deleteAllRolesForUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + throws StorageQueryException; }