Skip to content

Commit

Permalink
adds API for can create primary user
Browse files Browse the repository at this point in the history
  • Loading branch information
rishabhpoddar committed Aug 1, 2023
1 parent eb01313 commit 0816462
Show file tree
Hide file tree
Showing 4 changed files with 324 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/main/java/io/supertokens/webserver/Webserver.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.webserver.api.accountlinking.CanCreatePrimaryUserAPI;
import io.supertokens.webserver.api.core.*;
import io.supertokens.webserver.api.dashboard.*;
import io.supertokens.webserver.api.emailpassword.UserAPI;
Expand Down Expand Up @@ -249,6 +250,8 @@ private void setupRoutes() {
addAPI(new GetUserByIdAPI(main));
addAPI(new ListUsersByAccountInfoAPI(main));

addAPI(new CanCreatePrimaryUserAPI(main));

StandardContext context = tomcatReference.getContext();
Tomcat tomcat = tomcatReference.getTomcat();

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/supertokens/webserver/WebserverAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ public abstract class WebserverAPI extends HttpServlet {
supportedVersions.add(SemVer.v2_20);
supportedVersions.add(SemVer.v2_21);
supportedVersions.add(SemVer.v3_0);
supportedVersions.add(SemVer.v4_0);
}

public static SemVer getLatestCDIVersion() {
return SemVer.v3_0;
return SemVer.v4_0;
}

public SemVer getLatestCDIVersionForRequest(HttpServletRequest req)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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.webserver.api.accountlinking;

import com.google.gson.JsonObject;
import io.supertokens.AppIdentifierWithStorageAndUserIdMapping;
import io.supertokens.Main;
import io.supertokens.authRecipe.AuthRecipe;
import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException;
import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException;
import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.useridmapping.UserIdType;
import io.supertokens.webserver.InputParser;
import io.supertokens.webserver.WebserverAPI;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class CanCreatePrimaryUserAPI extends WebserverAPI {

public CanCreatePrimaryUserAPI(Main main) {
super(main, "");
}

@Override
public String getPath() {
return "/recipe/accountlinking/user/primary/check";
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
// API is app specific
String inputRecipeUserId = InputParser.getQueryParamOrThrowError(req, "recipeUserId", false);

try {
String userId = inputRecipeUserId;
AppIdentifierWithStorage appIdentifierWithStorage;
AppIdentifierWithStorageAndUserIdMapping mappingAndStorage =
getAppIdentifierWithStorageAndUserIdMappingFromRequest(
req, inputRecipeUserId, UserIdType.ANY);
if (mappingAndStorage.userIdMapping != null) {
userId = mappingAndStorage.userIdMapping.superTokensUserId;
}
appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage;

AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.canCreatePrimaryUser(appIdentifierWithStorage,
userId);
JsonObject response = new JsonObject();
response.addProperty("status", "OK");
response.addProperty("wasAlreadyAPrimaryUser", result.wasAlreadyAPrimaryUser);
super.sendJsonResponse(200, response, resp);
} catch (StorageQueryException | TenantOrAppNotFoundException e) {
throw new ServletException(e);
} catch (UnknownUserIdException e) {
throw new ServletException(new BadRequestException("Unknown user ID provided"));
} catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) {
JsonObject response = new JsonObject();
response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR");
response.addProperty("primaryUserId", e.primaryUserId);
response.addProperty("description", e.getMessage());
super.sendJsonResponse(200, response, resp);
} catch (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException e) {
JsonObject response = new JsonObject();
response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR");
response.addProperty("primaryUserId", e.primaryUserId);
response.addProperty("description", e.getMessage());
super.sendJsonResponse(200, response, resp);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*
* 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.ProcessState;
import io.supertokens.authRecipe.AuthRecipe;
import io.supertokens.emailpassword.EmailPassword;
import io.supertokens.featureflag.EE_FEATURES;
import io.supertokens.featureflag.FeatureFlagTestContent;
import io.supertokens.pluginInterface.STORAGE_TYPE;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.test.TestingProcessManager;
import io.supertokens.test.Utils;
import io.supertokens.test.httpRequest.HttpRequestForTesting;
import io.supertokens.test.httpRequest.HttpResponseException;
import io.supertokens.thirdparty.ThirdParty;
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.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

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

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

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

@Test
public void canCreateReturnsTrue() 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 user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234");

{
Map<String, String> params = new HashMap<>();
params.put("recipeUserId", user.id);

JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
"http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null,
WebserverAPI.getLatestCDIVersion().get(), "");
assertEquals(2, response.entrySet().size());
assertEquals("OK", response.get("status").getAsString());
assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean());
}

AuthRecipe.createPrimaryUser(process.main, user.id);

{
Map<String, String> params = new HashMap<>();
params.put("recipeUserId", user.id);

JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
"http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null,
WebserverAPI.getLatestCDIVersion().get(), "");
assertEquals(2, response.entrySet().size());
assertEquals("OK", response.get("status").getAsString());
assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean());
}

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

@Test
public void canCreatePrimaryUserBadInput() 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;
}

{
Map<String, String> params = new HashMap<>();

try {
HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
"http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null,
WebserverAPI.getLatestCDIVersion().get(), "");
assert (false);
} catch (HttpResponseException e) {
assert (e.statusCode == 400);
assert (e.getMessage()
.equals("Http error. Status Code: 400. Message: Field name 'recipeUserId' is missing in GET " +
"request"));
}
}

{
Map<String, String> params = new HashMap<>();
params.put("recipeUserId", "random");

try {
HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
"http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null,
WebserverAPI.getLatestCDIVersion().get(), "");
assert (false);
} catch (HttpResponseException e) {
assert (e.statusCode == 400);
assert (e.getMessage()
.equals("Http error. Status Code: 400. Message: Unknown user ID provided"));
}
}

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

@Test
public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser() 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));

AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com",
"pass1234");

AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id);
assert (!result.wasAlreadyAPrimaryUser);

ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google",
"test@example.com");

{
Map<String, String> params = new HashMap<>();
params.put("recipeUserId", signInUpResponse.user.id);

JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
"http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null,
WebserverAPI.getLatestCDIVersion().get(), "");
assertEquals(3, response.entrySet().size());
assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR",
response.get("status").getAsString());
assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString());
assertEquals("This user's email is already associated with another user ID",
response.get("description").getAsString());
}

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

@Test
public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() 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));

AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com",
"pass1234");
AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com",
"pass1234");

AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id);
AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id);

{
Map<String, String> params = new HashMap<>();
params.put("recipeUserId", emailPasswordUser2.id);

JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
"http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null,
WebserverAPI.getLatestCDIVersion().get(), "");
assertEquals(3, response.entrySet().size());
assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR",
response.get("status").getAsString());
assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString());
assertEquals("This user ID is already linked to another user ID",
response.get("description").getAsString());
}

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

}

0 comments on commit 0816462

Please sign in to comment.