Skip to content

Commit 9a8a366

Browse files
authored
fix: test email verified with user id mapping (#873)
* fix: email verified in login methods * fix: test * fix: version * fix: ev fix for inmemory
1 parent 7ec4fee commit 9a8a366

File tree

5 files changed

+203
-23
lines changed

5 files changed

+203
-23
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
66
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [7.0.9] - 2023-11-01
9+
10+
- Tests `verified` in `loginMethods` for users with userId mapping
11+
812
## [7.0.8] - 2023-10-19
913

1014
- Tests thirdParty serialization fix

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" }
1919
// }
2020
//}
2121

22-
version = "7.0.8"
22+
version = "7.0.9"
2323

2424

2525
repositories {

src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -243,26 +243,50 @@ public static List<String> isEmailVerified_transaction(Start start, Connection s
243243
return new ArrayList<>();
244244
}
245245
List<String> emails = new ArrayList<>();
246-
List<String> userIds = new ArrayList<>();
247-
Map<String, String> userIdToEmailMap = new HashMap<>();
246+
List<String> supertokensUserIds = new ArrayList<>();
248247
for (UserIdAndEmail ue : userIdAndEmail) {
249248
emails.add(ue.email);
250-
userIds.add(ue.userId);
249+
supertokensUserIds.add(ue.userId);
251250
}
251+
252+
// We have external user id stored in the email verification table, so we need to fetch the mapped userids for
253+
// calculating the verified emails
254+
255+
HashMap<String, String> supertokensUserIdToExternalUserIdMap = UserIdMappingQueries.getUserIdMappingWithUserIds_Transaction(start,
256+
sqlCon, supertokensUserIds);
257+
HashMap<String, String> externalUserIdToSupertokensUserIdMap = new HashMap<>();
258+
259+
List<String> supertokensOrExternalUserIdsToQuery = new ArrayList<>();
260+
for (String userId : supertokensUserIds) {
261+
if (supertokensUserIdToExternalUserIdMap.containsKey(userId)) {
262+
supertokensOrExternalUserIdsToQuery.add(supertokensUserIdToExternalUserIdMap.get(userId));
263+
externalUserIdToSupertokensUserIdMap.put(supertokensUserIdToExternalUserIdMap.get(userId), userId);
264+
} else {
265+
supertokensOrExternalUserIdsToQuery.add(userId);
266+
externalUserIdToSupertokensUserIdMap.put(userId, userId);
267+
}
268+
}
269+
270+
Map<String, String> supertokensOrExternalUserIdToEmailMap = new HashMap<>();
252271
for (UserIdAndEmail ue : userIdAndEmail) {
253-
if (userIdToEmailMap.containsKey(ue.userId)) {
272+
String supertokensOrExternalUserId = ue.userId;
273+
if (supertokensUserIdToExternalUserIdMap.containsKey(supertokensOrExternalUserId)) {
274+
supertokensOrExternalUserId = supertokensUserIdToExternalUserIdMap.get(supertokensOrExternalUserId);
275+
}
276+
if (supertokensOrExternalUserIdToEmailMap.containsKey(supertokensOrExternalUserId)) {
254277
throw new RuntimeException("Found a bug!");
255278
}
256-
userIdToEmailMap.put(ue.userId, ue.email);
279+
supertokensOrExternalUserIdToEmailMap.put(supertokensOrExternalUserId, ue.email);
257280
}
281+
258282
String QUERY = "SELECT * FROM " + getConfig(start).getEmailVerificationTable()
259-
+ " WHERE app_id = ? AND user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) +
283+
+ " WHERE app_id = ? AND user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(supertokensOrExternalUserIdsToQuery.size()) +
260284
") AND email IN (" + Utils.generateCommaSeperatedQuestionMarks(emails.size()) + ")";
261285

262286
return execute(sqlCon, QUERY, pst -> {
263287
pst.setString(1, appIdentifier.getAppId());
264288
int index = 2;
265-
for (String userId : userIds) {
289+
for (String userId : supertokensOrExternalUserIdsToQuery) {
266290
pst.setString(index++, userId);
267291
}
268292
for (String email : emails) {
@@ -271,10 +295,10 @@ public static List<String> isEmailVerified_transaction(Start start, Connection s
271295
}, result -> {
272296
List<String> res = new ArrayList<>();
273297
while (result.next()) {
274-
String userId = result.getString("user_id");
298+
String supertokensOrExternalUserId = result.getString("user_id");
275299
String email = result.getString("email");
276-
if (Objects.equals(userIdToEmailMap.get(userId), email)) {
277-
res.add(userId);
300+
if (Objects.equals(supertokensOrExternalUserIdToEmailMap.get(supertokensOrExternalUserId), email)) {
301+
res.add(externalUserIdToSupertokensUserIdMap.get(supertokensOrExternalUserId));
278302
}
279303
}
280304
return res;
@@ -288,26 +312,46 @@ public static List<String> isEmailVerified(Start start, AppIdentifier appIdentif
288312
return new ArrayList<>();
289313
}
290314
List<String> emails = new ArrayList<>();
291-
List<String> userIds = new ArrayList<>();
292-
Map<String, String> userIdToEmailMap = new HashMap<>();
315+
List<String> supertokensUserIds = new ArrayList<>();
316+
293317
for (UserIdAndEmail ue : userIdAndEmail) {
294318
emails.add(ue.email);
295-
userIds.add(ue.userId);
319+
supertokensUserIds.add(ue.userId);
320+
}
321+
// We have external user id stored in the email verification table, so we need to fetch the mapped userids for
322+
// calculating the verified emails
323+
HashMap<String, String> supertokensUserIdToExternalUserIdMap = UserIdMappingQueries.getUserIdMappingWithUserIds(start,
324+
supertokensUserIds);
325+
HashMap<String, String> externalUserIdToSupertokensUserIdMap = new HashMap<>();
326+
List<String> supertokensOrExternalUserIdsToQuery = new ArrayList<>();
327+
for (String userId : supertokensUserIds) {
328+
if (supertokensUserIdToExternalUserIdMap.containsKey(userId)) {
329+
supertokensOrExternalUserIdsToQuery.add(supertokensUserIdToExternalUserIdMap.get(userId));
330+
externalUserIdToSupertokensUserIdMap.put(supertokensUserIdToExternalUserIdMap.get(userId), userId);
331+
} else {
332+
supertokensOrExternalUserIdsToQuery.add(userId);
333+
externalUserIdToSupertokensUserIdMap.put(userId, userId);
334+
}
296335
}
336+
337+
Map<String, String> supertokensOrExternalUserIdToEmailMap = new HashMap<>();
297338
for (UserIdAndEmail ue : userIdAndEmail) {
298-
if (userIdToEmailMap.containsKey(ue.userId)) {
339+
String supertokensOrExternalUserId = ue.userId;
340+
if (supertokensUserIdToExternalUserIdMap.containsKey(supertokensOrExternalUserId)) {
341+
supertokensOrExternalUserId = supertokensUserIdToExternalUserIdMap.get(supertokensOrExternalUserId);
342+
}
343+
if (supertokensOrExternalUserIdToEmailMap.containsKey(supertokensOrExternalUserId)) {
299344
throw new RuntimeException("Found a bug!");
300345
}
301-
userIdToEmailMap.put(ue.userId, ue.email);
346+
supertokensOrExternalUserIdToEmailMap.put(supertokensOrExternalUserId, ue.email);
302347
}
303348
String QUERY = "SELECT * FROM " + getConfig(start).getEmailVerificationTable()
304-
+ " WHERE app_id = ? AND user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) +
349+
+ " WHERE app_id = ? AND user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(supertokensOrExternalUserIdsToQuery.size()) +
305350
") AND email IN (" + Utils.generateCommaSeperatedQuestionMarks(emails.size()) + ")";
306-
307351
return execute(start, QUERY, pst -> {
308352
pst.setString(1, appIdentifier.getAppId());
309353
int index = 2;
310-
for (String userId : userIds) {
354+
for (String userId : supertokensOrExternalUserIdsToQuery) {
311355
pst.setString(index++, userId);
312356
}
313357
for (String email : emails) {
@@ -316,10 +360,10 @@ public static List<String> isEmailVerified(Start start, AppIdentifier appIdentif
316360
}, result -> {
317361
List<String> res = new ArrayList<>();
318362
while (result.next()) {
319-
String userId = result.getString("user_id");
363+
String supertokensOrExternalUserId = result.getString("user_id");
320364
String email = result.getString("email");
321-
if (Objects.equals(userIdToEmailMap.get(userId), email)) {
322-
res.add(userId);
365+
if (Objects.equals(supertokensOrExternalUserIdToEmailMap.get(supertokensOrExternalUserId), email)) {
366+
res.add(externalUserIdToSupertokensUserIdMap.get(supertokensOrExternalUserId));
323367
}
324368
}
325369
return res;

src/main/java/io/supertokens/inmemorydb/queries/UserIdMappingQueries.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.sql.SQLException;
3030
import java.util.ArrayList;
3131
import java.util.HashMap;
32+
import java.util.List;
3233

3334
import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute;
3435
import static io.supertokens.inmemorydb.QueryExecutorTemplate.update;
@@ -135,7 +136,7 @@ public static UserIdMapping[] getUserIdMappingWithEitherSuperTokensUserIdOrExter
135136

136137
}
137138

138-
public static HashMap<String, String> getUserIdMappingWithUserIds(Start start, ArrayList<String> userIds)
139+
public static HashMap<String, String> getUserIdMappingWithUserIds(Start start, List<String> userIds)
139140
throws SQLException, StorageQueryException {
140141

141142
if (userIds.size() == 0) {
@@ -168,6 +169,39 @@ public static HashMap<String, String> getUserIdMappingWithUserIds(Start start, A
168169
});
169170
}
170171

172+
public static HashMap<String, String> getUserIdMappingWithUserIds_Transaction(Start start, Connection sqlCon, List<String> userIds)
173+
throws SQLException, StorageQueryException {
174+
175+
if (userIds.size() == 0) {
176+
return new HashMap<>();
177+
}
178+
179+
// No need to filter based on tenantId because the id list is already filtered for a tenant
180+
StringBuilder QUERY = new StringBuilder(
181+
"SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + " WHERE supertokens_user_id IN (");
182+
for (int i = 0; i < userIds.size(); i++) {
183+
QUERY.append("?");
184+
if (i != userIds.size() - 1) {
185+
// not the last element
186+
QUERY.append(",");
187+
}
188+
}
189+
QUERY.append(")");
190+
return execute(sqlCon, QUERY.toString(), pst -> {
191+
for (int i = 0; i < userIds.size(); i++) {
192+
// i+1 cause this starts with 1 and not 0
193+
pst.setString(i + 1, userIds.get(i));
194+
}
195+
}, result -> {
196+
HashMap<String, String> userIdMappings = new HashMap<>();
197+
while (result.next()) {
198+
UserIdMapping temp = UserIdMappingRowMapper.getInstance().mapOrThrow(result);
199+
userIdMappings.put(temp.superTokensUserId, temp.externalUserId);
200+
}
201+
return userIdMappings;
202+
});
203+
}
204+
171205
public static boolean deleteUserIdMappingWithSuperTokensUserId(Start start, AppIdentifier appIdentifier, String userId)
172206
throws SQLException, StorageQueryException {
173207
String QUERY = "DELETE FROM " + Config.getConfig(start).getUserIdMappingTable()
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved.
3+
*
4+
* This software is licensed under the Apache License, Version 2.0 (the
5+
* "License") as published by the Apache Software Foundation.
6+
*
7+
* You may not use this file except in compliance with the License. You may
8+
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package io.supertokens.test.accountlinking.api;
18+
19+
import com.google.gson.JsonObject;
20+
import io.supertokens.ProcessState;
21+
import io.supertokens.authRecipe.AuthRecipe;
22+
import io.supertokens.emailpassword.EmailPassword;
23+
import io.supertokens.emailverification.EmailVerification;
24+
import io.supertokens.featureflag.EE_FEATURES;
25+
import io.supertokens.featureflag.FeatureFlagTestContent;
26+
import io.supertokens.pluginInterface.STORAGE_TYPE;
27+
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
28+
import io.supertokens.storageLayer.StorageLayer;
29+
import io.supertokens.test.TestingProcessManager;
30+
import io.supertokens.test.Utils;
31+
import io.supertokens.test.httpRequest.HttpRequestForTesting;
32+
import io.supertokens.useridmapping.UserIdMapping;
33+
import io.supertokens.webserver.WebserverAPI;
34+
import org.junit.AfterClass;
35+
import org.junit.Before;
36+
import org.junit.Rule;
37+
import org.junit.Test;
38+
import org.junit.rules.TestRule;
39+
40+
import java.util.HashMap;
41+
import java.util.Map;
42+
43+
import static org.junit.Assert.*;
44+
45+
public class EmailVerificationTest {
46+
@Rule
47+
public TestRule watchman = Utils.getOnFailure();
48+
49+
@AfterClass
50+
public static void afterTesting() {
51+
Utils.afterTesting();
52+
}
53+
54+
@Before
55+
public void beforeEach() {
56+
Utils.reset();
57+
}
58+
59+
@Test
60+
public void testEmailVerificationWithUserIdMapping() throws Exception {
61+
String[] args = {"../"};
62+
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
63+
FeatureFlagTestContent.getInstance(process.getProcess())
64+
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
65+
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
66+
process.startProcess();
67+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
68+
69+
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
70+
return;
71+
}
72+
73+
AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "[email protected]", "password");
74+
UserIdMapping.createUserIdMapping(process.getProcess(), user1.getSupertokensUserId(), "euserid1", null, false);
75+
String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), "euserid1", "[email protected]");
76+
EmailVerification.verifyEmail(process.getProcess(), token);
77+
78+
AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "[email protected]", "password");
79+
UserIdMapping.createUserIdMapping(process.getProcess(), user2.getSupertokensUserId(), "euserid2", null, false);
80+
81+
AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId());
82+
AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId());
83+
84+
{
85+
Map<String, String> params = new HashMap<>();
86+
params.put("userId", "euserid1");
87+
JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
88+
"http://localhost:3567/user/id", params, 1000, 1000, null,
89+
WebserverAPI.getLatestCDIVersion().get(), "");
90+
JsonObject user = response.get("user").getAsJsonObject();
91+
assertTrue(user.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject().get("verified").getAsBoolean());
92+
assertFalse(user.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject().get("verified").getAsBoolean());
93+
}
94+
95+
process.kill();
96+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
97+
}
98+
}

0 commit comments

Comments
 (0)