Skip to content

Commit

Permalink
fix: remove active user of recipe user when linked (#773)
Browse files Browse the repository at this point in the history
* fix: remove active user of recipe user when linked

* fix: query
  • Loading branch information
sattvikc authored Aug 28, 2023
1 parent 446d282 commit 97eca33
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 0 deletions.
16 changes: 16 additions & 0 deletions src/main/java/io/supertokens/ActiveUsers.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.supertokens;

import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.storageLayer.StorageLayer;
Expand Down Expand Up @@ -37,4 +39,18 @@ public static int countUsersActiveSince(Main main, long time)
return countUsersActiveSince(new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(main)), main,
time);
}

public static void removeActiveUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId)
throws StorageQueryException {
try {
((AuthRecipeSQLStorage) appIdentifierWithStorage.getActiveUsersStorage()).startTransaction(con -> {
appIdentifierWithStorage.getActiveUsersStorage().deleteUserActive_Transaction(con, appIdentifierWithStorage, userId);
((AuthRecipeSQLStorage) appIdentifierWithStorage.getActiveUsersStorage()).commitTransaction(con);
return null;
});

} catch (StorageTransactionLogicException e) {
throw new StorageQueryException(e.actualException);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.supertokens.webserver.api.accountlinking;

import com.google.gson.JsonObject;
import io.supertokens.ActiveUsers;
import io.supertokens.AppIdentifierWithStorageAndUserIdMapping;
import io.supertokens.Main;
import io.supertokens.authRecipe.AuthRecipe;
Expand All @@ -26,6 +27,7 @@
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage;
Expand All @@ -39,6 +41,8 @@
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class LinkAccountsAPI extends WebserverAPI {

Expand Down Expand Up @@ -96,6 +100,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
AuthRecipe.LinkAccountsResult linkAccountsResult = AuthRecipe.linkAccounts(main,
primaryUserIdAppIdentifierWithStorage,
recipeUserId, primaryUserId);
// Remove linked account user id from active user
ActiveUsers.removeActiveUser(recipeUserIdAppIdentifierWithStorage, recipeUserId);

UserIdMapping.populateExternalUserIdForUsers(primaryUserIdAppIdentifierWithStorage, new AuthRecipeUserInfo[]{linkAccountsResult.user});
JsonObject response = new JsonObject();
response.addProperty("status", "OK");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* 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.test.accountlinking.api;

import com.google.gson.JsonObject;
import io.supertokens.ActiveUsers;
import io.supertokens.Main;
import io.supertokens.ProcessState;
import io.supertokens.authRecipe.AuthRecipe;
import io.supertokens.emailpassword.EmailPassword;
import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException;
import io.supertokens.featureflag.EE_FEATURES;
import io.supertokens.featureflag.FeatureFlagTestContent;
import io.supertokens.passwordless.Passwordless;
import io.supertokens.passwordless.exceptions.*;
import io.supertokens.pluginInterface.STORAGE_TYPE;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException;
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.test.TestingProcessManager;
import io.supertokens.test.Utils;
import io.supertokens.test.httpRequest.HttpRequestForTesting;
import io.supertokens.thirdparty.ThirdParty;
import io.supertokens.utils.SemVer;
import io.supertokens.webserver.WebserverAPI;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class ActiveUserTest {
@Rule
public TestRule watchman = Utils.getOnFailure();

@AfterClass
public static void afterTesting() {
Utils.afterTesting();
}

@Before
public void beforeEach() {
Utils.reset();
}


AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password)
throws DuplicateEmailException, StorageQueryException {
return EmailPassword.signUp(main, email, password);
}

AuthRecipeUserInfo createThirdPartyUser(Main main, String thirdPartyId, String thirdPartyUserId, String email)
throws EmailChangeNotAllowedException, StorageQueryException {
return ThirdParty.signInUp(main, thirdPartyId, thirdPartyUserId, email).user;
}

AuthRecipeUserInfo createPasswordlessUserWithEmail(Main main, String email)
throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException,
RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException,
StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException {
Passwordless.CreateCodeResponse code = Passwordless.createCode(main, email, null,
null, "123456");
return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash,
code.userInputCode, null).user;
}

AuthRecipeUserInfo createPasswordlessUserWithPhone(Main main, String phone)
throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException,
RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException,
StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException {
Passwordless.CreateCodeResponse code = Passwordless.createCode(main, null, phone,
null, "123456");
return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash,
code.userInputCode, null).user;
}

@Test
public void testActiveUserIsRemovedAfterLinkingAccounts() throws Exception {
String[] args = {"../"};
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
FeatureFlagTestContent.getInstance(process.getProcess())
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
process.startProcess();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));

if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
return;
}

AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "[email protected]", "password");
AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "google-user", "[email protected]");

{
// Update active user
JsonObject responseBody = new JsonObject();
responseBody.addProperty("email", "[email protected]");
responseBody.addProperty("password", "password");

HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
"http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(),
"emailpassword");
}
{
JsonObject emailObject = new JsonObject();
emailObject.addProperty("id", "[email protected]");

JsonObject signUpRequestBody = new JsonObject();
signUpRequestBody.addProperty("thirdPartyId", "google");
signUpRequestBody.addProperty("thirdPartyUserId", "google-user");
signUpRequestBody.add("email", emailObject);

HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
"http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null,
SemVer.v4_0.get(), "thirdparty");
}

int userCount = ActiveUsers.countUsersActiveSince(process.getProcess(), System.currentTimeMillis() - 10000);
assertEquals(2, userCount);

{
// Link accounts
AuthRecipe.createPrimaryUser(process.main, user2.getSupertokensUserId());

JsonObject params = new JsonObject();
params.addProperty("recipeUserId", user1.getSupertokensUserId());
params.addProperty("primaryUserId", user2.getSupertokensUserId());

HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
"http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null,
WebserverAPI.getLatestCDIVersion().get(), "");
}

// Now there should be only one active user
userCount = ActiveUsers.countUsersActiveSince(process.getProcess(), System.currentTimeMillis() - 10000);
assertEquals(1, userCount);

// Sign in to the accounts once again
{
// Update active user
JsonObject responseBody = new JsonObject();
responseBody.addProperty("email", "[email protected]");
responseBody.addProperty("password", "password");

HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
"http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(),
"emailpassword");
}
{
JsonObject emailObject = new JsonObject();
emailObject.addProperty("id", "[email protected]");

JsonObject signUpRequestBody = new JsonObject();
signUpRequestBody.addProperty("thirdPartyId", "google");
signUpRequestBody.addProperty("thirdPartyUserId", "google-user");
signUpRequestBody.add("email", emailObject);

HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
"http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null,
SemVer.v4_0.get(), "thirdparty");
}

// there should still be only one active user
userCount = ActiveUsers.countUsersActiveSince(process.getProcess(), System.currentTimeMillis() - 10000);
assertEquals(1, userCount);

process.kill();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
}
}

0 comments on commit 97eca33

Please sign in to comment.