Skip to content

Commit 6b7e443

Browse files
authored
fix: enforce public tenant in session APIs (#964)
1 parent 08355c7 commit 6b7e443

File tree

3 files changed

+102
-20
lines changed

3 files changed

+102
-20
lines changed

src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,14 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
113113
String[] sessionHandlesRevoked;
114114

115115
if (revokeAcrossAllTenants) {
116-
// when revokeAcrossAllTenants is true, and given that the backend SDK might pass tenant id
117-
// we do not want to enforce public tenant here but behave as if this is an app specific API
118-
// So instead of calling enforcePublicTenantAndGetAllStoragesForApp, we simply do all the logic
119-
// here to fetch the storages and find the storage where `userId` exists. If user id does not
120-
// exist, we use the storage for the tenantId passed in the request.
121116
AppIdentifier appIdentifier = getAppIdentifier(req);
122-
Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier);
123117
try {
124-
StorageAndUserIdMapping storageAndUserIdMapping = StorageLayer.findStorageAndUserIdMappingForUser(
125-
appIdentifier, storages, userId, UserIdType.ANY);
118+
StorageAndUserIdMapping storageAndUserIdMapping =
119+
enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(
120+
req, userId, UserIdType.ANY, false);
126121
storage = storageAndUserIdMapping.storage;
127122
} catch (UnknownUserIdException e) {
128-
storage = getTenantStorage(req);
123+
throw new IllegalStateException("should never happen");
129124
}
130125
sessionHandlesRevoked = Session.revokeAllSessionsForUser(
131126
main, appIdentifier, storage, userId, revokeSessionsForLinkedAccounts);
@@ -159,7 +154,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
159154
}
160155
result.add("sessionHandlesRevoked", sessionHandlesRevokedJSON);
161156
super.sendJsonResponse(200, result, resp);
162-
} catch (StorageQueryException | TenantOrAppNotFoundException e) {
157+
} catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) {
163158
throw new ServletException(e);
164159
}
165160
} else {

src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,15 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO
7777
String[] sessionHandles;
7878

7979
if (fetchAcrossAllTenants) {
80-
// when fetchAcrossAllTenants is true, and given that the backend SDK might pass tenant id
81-
// we do not want to enforce public tenant here but behave as if this is an app specific API
82-
// So instead of calling enforcePublicTenantAndGetAllStoragesForApp, we simply do all the logic
83-
// here to fetch the storages and find the storage where `userId` exists. If user id does not
84-
// exist, we use the storage for the tenantId passed in the request.
8580
AppIdentifier appIdentifier = getAppIdentifier(req);
86-
Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier);
8781
Storage storage;
8882
try {
8983
StorageAndUserIdMapping storageAndUserIdMapping =
90-
StorageLayer.findStorageAndUserIdMappingForUser(
91-
appIdentifier, storages, userId, UserIdType.ANY);
84+
enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(
85+
req, userId, UserIdType.ANY, false);
9286
storage = storageAndUserIdMapping.storage;
9387
} catch (UnknownUserIdException e) {
94-
storage = getTenantStorage(req);
88+
throw new IllegalStateException("should never happen");
9589
}
9690
sessionHandles = Session.getAllNonExpiredSessionHandlesForUser(
9791
main, appIdentifier, storage, userId,
@@ -112,7 +106,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO
112106
result.add("sessionHandles", arr);
113107
super.sendJsonResponse(200, result, resp);
114108

115-
} catch (StorageQueryException | TenantOrAppNotFoundException e) {
109+
} catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) {
116110
throw new ServletException(e);
117111
}
118112
}

src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,24 @@
3434
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
3535
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
3636
import io.supertokens.pluginInterface.usermetadata.sqlStorage.UserMetadataSQLStorage;
37+
import io.supertokens.session.Session;
38+
import io.supertokens.session.info.SessionInformationHolder;
3739
import io.supertokens.storageLayer.StorageLayer;
3840
import io.supertokens.test.TestingProcessManager;
3941
import io.supertokens.test.Utils;
42+
import io.supertokens.test.httpRequest.HttpRequestForTesting;
4043
import io.supertokens.test.httpRequest.HttpResponseException;
4144
import io.supertokens.thirdparty.InvalidProviderConfigException;
4245
import io.supertokens.useridmapping.UserIdMapping;
46+
import io.supertokens.utils.SemVer;
4347
import org.junit.After;
4448
import org.junit.AfterClass;
4549
import org.junit.Before;
4650
import org.junit.Test;
4751

4852
import java.io.IOException;
53+
import java.util.HashMap;
54+
import java.util.Map;
4955

5056
import static org.junit.Assert.*;
5157

@@ -375,4 +381,91 @@ public void testEmailVerificationWithUsersOnDifferentTenantStorages() throws Exc
375381
assertFalse(t1EvStorage.isEmailVerified(t0.toAppIdentifier(), user2.getSupertokensUserId(), "[email protected]")); // ensure t1 storage does not have user2's ev
376382
assertFalse(t0EvStorage.isEmailVerified(t0.toAppIdentifier(), user1.getSupertokensUserId(), "[email protected]")); // ensure t0 storage does not have user1's ev
377383
}
384+
385+
@Test
386+
public void testSessionCannotGetAcrossAllStorageOrRevokedAcrossAllTenantsFromNonPublicTenant() throws Exception {
387+
if (StorageLayer.getBaseStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
388+
return;
389+
}
390+
391+
if (StorageLayer.isInMemDb(process.getProcess())) {
392+
return;
393+
}
394+
395+
TenantIdentifier t0 = new TenantIdentifier(null, null, null);
396+
Storage t0Storage = (StorageLayer.getStorage(t0, process.getProcess()));
397+
398+
TenantIdentifier t1 = new TenantIdentifier(null, null, "t1");
399+
Storage t1Storage = (StorageLayer.getStorage(t1, process.getProcess()));
400+
401+
// Create users
402+
AuthRecipeUserInfo user1 = EmailPassword.signUp(t0, t0Storage, process.getProcess(), "[email protected]", "password123");
403+
AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "[email protected]", "password123");
404+
405+
UserIdMapping.populateExternalUserIdForUsers(t0.toAppIdentifier(), t0Storage, new AuthRecipeUserInfo[]{user1});
406+
UserIdMapping.populateExternalUserIdForUsers(t1.toAppIdentifier(), t1Storage, new AuthRecipeUserInfo[]{user2});
407+
408+
SessionInformationHolder sess1 = Session.createNewSession(t0, t0Storage,
409+
process.getProcess(), user1.getSupertokensUserId(), new JsonObject(), new JsonObject());
410+
SessionInformationHolder sess2 = Session.createNewSession(t1, t1Storage,
411+
process.getProcess(), user2.getSupertokensUserId(), new JsonObject(), new JsonObject());
412+
413+
{
414+
Map<String, String> params = new HashMap<>();
415+
params.put("fetchAcrossAllTenants", "true");
416+
params.put("userId", user1.getSupertokensUserId());
417+
418+
JsonObject sessionResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
419+
HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/user"),
420+
params, 1000, 1000, null, SemVer.v4_0.get(),
421+
"session");
422+
assertEquals("OK", sessionResponse.get("status").getAsString());
423+
assertEquals(1, sessionResponse.get("sessionHandles").getAsJsonArray().size());
424+
assertEquals(sess1.session.handle, sessionResponse.get("sessionHandles").getAsJsonArray().get(0).getAsString());
425+
}
426+
427+
{
428+
try {
429+
Map<String, String> params = new HashMap<>();
430+
params.put("fetchAcrossAllTenants", "true");
431+
params.put("userId", user1.getSupertokensUserId());
432+
433+
JsonObject sessionResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
434+
HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/user"),
435+
params, 1000, 1000, null, SemVer.v5_0.get(),
436+
"session");
437+
fail();
438+
} catch (HttpResponseException e) {
439+
assertEquals(403, e.statusCode);
440+
}
441+
}
442+
443+
{
444+
try {
445+
JsonObject requestBody = new JsonObject();
446+
requestBody.addProperty("userId", user1.getSupertokensUserId());
447+
requestBody.addProperty("revokeAcrossAllTenants", true);
448+
449+
JsonObject sessionResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
450+
HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/remove"), requestBody,
451+
1000, 1000, null, SemVer.v5_0.get(),
452+
"session");
453+
fail();
454+
} catch (HttpResponseException e) {
455+
assertEquals(403, e.statusCode);
456+
}
457+
}
458+
459+
{
460+
JsonObject requestBody = new JsonObject();
461+
requestBody.addProperty("userId", user1.getSupertokensUserId());
462+
requestBody.addProperty("revokeAcrossAllTenants", true);
463+
464+
JsonObject sessionResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
465+
HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/session/remove"), requestBody,
466+
1000, 1000, null, SemVer.v4_0.get(),
467+
"session");
468+
assertEquals("OK", sessionResponse.get("status").getAsString());
469+
}
470+
}
378471
}

0 commit comments

Comments
 (0)