Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: tests #776

Merged
merged 14 commits into from
Sep 6, 2023
15 changes: 11 additions & 4 deletions src/main/java/io/supertokens/authRecipe/AuthRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,9 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon
.listPrimaryUsersByPhoneNumber_Transaction(appIdentifierWithStorage, con,
loginMethod.phoneNumber);
for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) {
if (!user.tenantIds.contains(tenantId)) {
continue;
}
if (user.isPrimaryUser) {
throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.getSupertokensUserId(),
"This user's phone number is already associated with another user" +
Expand Down Expand Up @@ -817,6 +820,10 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit
if (removeAllLinkedAccounts || userToDelete.loginMethods.length == 1) {
if (userToDelete.getSupertokensUserId().equals(userIdToDeleteForAuthRecipe)) {
primaryUserIdToDeleteNonAuthRecipe = userIdToDeleteForNonAuthRecipeForRecipeUserId;
if (primaryUserIdToDeleteNonAuthRecipe == null) {
deleteAuthRecipeUser(con, appIdentifierWithStorage, userToDelete.getSupertokensUserId(),
true);
}
} else {
// this is always type supertokens user ID cause it's from a user from the database.
io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult =
Expand Down Expand Up @@ -918,15 +925,15 @@ private static void deleteNonAuthRecipeUser(TransactionConnection con, AppIdenti

private static void deleteAuthRecipeUser(TransactionConnection con,
AppIdentifierWithStorage appIdentifierWithStorage, String
userId, boolean deleteUserIdMappingToo)
userId, boolean deleteFromUserIdToAppIdTableToo)
throws StorageQueryException {
// auth recipe deletions here only
appIdentifierWithStorage.getEmailPasswordStorage()
.deleteEmailPasswordUser_Transaction(con, appIdentifierWithStorage, userId, deleteUserIdMappingToo);
.deleteEmailPasswordUser_Transaction(con, appIdentifierWithStorage, userId, deleteFromUserIdToAppIdTableToo);
appIdentifierWithStorage.getThirdPartyStorage()
.deleteThirdPartyUser_Transaction(con, appIdentifierWithStorage, userId, deleteUserIdMappingToo);
.deleteThirdPartyUser_Transaction(con, appIdentifierWithStorage, userId, deleteFromUserIdToAppIdTableToo);
appIdentifierWithStorage.getPasswordlessStorage()
.deletePasswordlessUser_Transaction(con, appIdentifierWithStorage, userId, deleteUserIdMappingToo);
.deletePasswordlessUser_Transaction(con, appIdentifierWithStorage, userId, deleteFromUserIdToAppIdTableToo);
}

public static boolean deleteNonAuthRecipeUser(TenantIdentifierWithStorage
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -2786,6 +2786,14 @@ public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentif
// }
}

@Override
public AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo(AppIdentifier appIdentifier,
String thirdPartyId,
String thirdPartyUserId)
throws StorageQueryException {
return null; // TODO
}

@Override
public AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_Transaction(AppIdentifier appIdentifier,
TransactionConnection con,
Expand Down
74 changes: 69 additions & 5 deletions src/main/java/io/supertokens/multitenancy/Multitenancy.java
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,9 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t
String userId)
throws TenantOrAppNotFoundException, UnknownUserIdException, StorageQueryException,
FeatureNotEnabledException, DuplicateEmailException, DuplicatePhoneNumberException,
DuplicateThirdPartyUserException {
DuplicateThirdPartyUserException, AnotherPrimaryUserWithPhoneNumberAlreadyExistsException,
AnotherPrimaryUserWithEmailAlreadyExistsException,
AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException {
if (Arrays.stream(FeatureFlag.getInstance(main, new AppIdentifier(null, null)).getEnabledFeatures())
.noneMatch(ee_features -> ee_features == EE_FEATURES.MULTI_TENANCY)) {
throw new FeatureNotEnabledException(EE_FEATURES.MULTI_TENANCY);
Expand All @@ -396,7 +398,7 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t
String tenantId = tenantIdentifierWithStorage.getTenantId();
AuthRecipeUserInfo userToAssociate = storage.getPrimaryUserById_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, userId);

if (userToAssociate.isPrimaryUser) {
if (userToAssociate != null && userToAssociate.isPrimaryUser) {
Set<String> emails = new HashSet<>();
Set<String> phoneNumbers = new HashSet<>();
Set<LoginMethod.ThirdParty> thirdParties = new HashSet<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the checks below for same email etc for account linking should check:

  • if the login method is the same, throw duplicaremalexception etc..
  • if it's a different login method, then we shuold throw a new exception type -> UserTenantAssociationNotAllowed with a reason string -> if this is on the backend SDK, then we should have the status reflected in there too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the other places where this type of check also happens consider the login method being different?

Expand All @@ -418,7 +420,16 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t
AuthRecipeUserInfo[] users = storage.listPrimaryUsersByEmail_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, email);
for (AuthRecipeUserInfo user : users) {
if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) {
throw new StorageTransactionLogicException(new DuplicateEmailException());
for (LoginMethod lm1 : user.loginMethods) {
if (lm1.tenantIds.contains(tenantId)) {
for (LoginMethod lm2 : userToAssociate.loginMethods) {
if (lm1.recipeId.equals(lm2.recipeId) && email.equals(lm1.email) && lm1.email.equals(lm2.email)) {
throw new StorageTransactionLogicException(new DuplicateEmailException());
}
}
}
}
throw new StorageTransactionLogicException(new AnotherPrimaryUserWithEmailAlreadyExistsException(user.getSupertokensUserId()));
}
}
}
Expand All @@ -427,7 +438,16 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t
AuthRecipeUserInfo[] users = storage.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, phoneNumber);
for (AuthRecipeUserInfo user : users) {
if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) {
throw new StorageTransactionLogicException(new DuplicatePhoneNumberException());
for (LoginMethod lm1 : user.loginMethods) {
if (lm1.tenantIds.contains(tenantId)) {
for (LoginMethod lm2 : userToAssociate.loginMethods) {
if (lm1.recipeId.equals(lm2.recipeId) && phoneNumber.equals(lm1.phoneNumber) && lm1.phoneNumber.equals(lm2.phoneNumber)) {
throw new StorageTransactionLogicException(new DuplicatePhoneNumberException());
}
}
}
}
throw new StorageTransactionLogicException(new AnotherPrimaryUserWithPhoneNumberAlreadyExistsException(user.getSupertokensUserId()));
}
}
}
Expand All @@ -436,12 +456,25 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t
AuthRecipeUserInfo[] users = storage.listPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, tp.id, tp.userId);
for (AuthRecipeUserInfo user : users) {
if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) {
throw new StorageTransactionLogicException(new DuplicateThirdPartyUserException());
for (LoginMethod lm1 : user.loginMethods) {
if (lm1.tenantIds.contains(tenantId)) {
for (LoginMethod lm2 : userToAssociate.loginMethods) {
if (lm1.recipeId.equals(lm2.recipeId) && tp.equals(lm1.thirdParty) && lm1.thirdParty.equals(lm2.thirdParty)) {
throw new StorageTransactionLogicException(new DuplicateThirdPartyUserException());
}
}
}
}

throw new StorageTransactionLogicException(new AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException(user.getSupertokensUserId()));
}
}
}
}

// userToAssociate may be null if the user is not associated to any tenants, we can still try and
// associate it. This happens only in CDI 3.0 where we allow disassociation from all tenants
// This will not happen in CDI >= 4.0 because we will not allow disassociation from all tenants
try {
boolean result = ((MultitenancySQLStorage) storage).addUserIdToTenant_Transaction(tenantIdentifierWithStorage, con, userId);
storage.commitTransaction(con);
Expand All @@ -462,20 +495,51 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t
throw (TenantOrAppNotFoundException) e.actualException;
} else if (e.actualException instanceof UnknownUserIdException) {
throw (UnknownUserIdException) e.actualException;
} else if (e.actualException instanceof AnotherPrimaryUserWithPhoneNumberAlreadyExistsException) {
throw (AnotherPrimaryUserWithPhoneNumberAlreadyExistsException) e.actualException;
} else if (e.actualException instanceof AnotherPrimaryUserWithEmailAlreadyExistsException) {
throw (AnotherPrimaryUserWithEmailAlreadyExistsException) e.actualException;
} else if (e.actualException instanceof AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException) {
throw (AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException) e.actualException;
}
throw new StorageQueryException(e.actualException);
}
}

@TestOnly
public static boolean removeUserIdFromTenant(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage,
String userId, String externalUserId)
throws FeatureNotEnabledException, TenantOrAppNotFoundException, StorageQueryException,
UnknownUserIdException {
try {
return removeUserIdFromTenant(main, tenantIdentifierWithStorage, userId, externalUserId, false);
} catch (DisassociationNotAllowedException e) {
throw new IllegalStateException("should never happen");
}
}

public static boolean removeUserIdFromTenant(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage,
String userId, String externalUserId, boolean disallowLastTenantDisassociation)
throws FeatureNotEnabledException, TenantOrAppNotFoundException, StorageQueryException,
UnknownUserIdException, DisassociationNotAllowedException {
if (Arrays.stream(FeatureFlag.getInstance(main, new AppIdentifier(null, null)).getEnabledFeatures())
.noneMatch(ee_features -> ee_features == EE_FEATURES.MULTI_TENANCY)) {
throw new FeatureNotEnabledException(EE_FEATURES.MULTI_TENANCY);
}

if (disallowLastTenantDisassociation) {
AuthRecipeUserInfo userInfo = AuthRecipe.getUserById(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), userId);
if (userInfo != null) {
for (LoginMethod lM : userInfo.loginMethods) {
if (lM.getSupertokensUserId().equals(userId)) {
if (lM.tenantIds.size() == 1 && lM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) {
throw new DisassociationNotAllowedException();
}
}
}
}
}

boolean finalDidExist = false;
boolean didExist = AuthRecipe.deleteNonAuthRecipeUser(tenantIdentifierWithStorage,
externalUserId == null ? userId : externalUserId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.multitenancy.exception;

public class AnotherPrimaryUserWithEmailAlreadyExistsException extends Exception {
public AnotherPrimaryUserWithEmailAlreadyExistsException(String primaryUserId) {
super("Another primary user with email already exists: " + primaryUserId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.multitenancy.exception;

public class AnotherPrimaryUserWithPhoneNumberAlreadyExistsException extends Exception {
public AnotherPrimaryUserWithPhoneNumberAlreadyExistsException(String primaryUserId) {
super("Another primary user with phone number already exists: " + primaryUserId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.multitenancy.exception;

public class AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException extends Exception {
public AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException(String primaryUserId) {
super("Another primary user with third party info already exists: " + primaryUserId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.multitenancy.exception;

public class DisassociationNotAllowedException extends Exception {
public DisassociationNotAllowedException() {
super("Disassociation not allowed");
}
}
Loading
Loading