From d921477d288b9e2823a177e3c2a35e22f718ed87 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 21 Mar 2024 16:17:58 +0530 Subject: [PATCH] fix: backports for core 8.0 (#968) * fix: userIdMapping queries * fix: session * fix: active users with account linking * fix: refresh session * fix: session refresh * fix: tests --- CHANGELOG.md | 9 + build.gradle | 2 +- src/main/java/io/supertokens/ActiveUsers.java | 16 ++ .../io/supertokens/authRecipe/AuthRecipe.java | 2 + .../java/io/supertokens/inmemorydb/Start.java | 9 +- .../queries/EmailVerificationQueries.java | 4 +- .../inmemorydb/queries/SessionQueries.java | 11 +- .../queries/UserIdMappingQueries.java | 24 +- .../java/io/supertokens/session/Session.java | 67 ++++-- .../useridmapping/UserIdMapping.java | 11 +- .../accountlinking/CanLinkAccountsAPI.java | 2 +- .../api/accountlinking/LinkAccountsAPI.java | 17 +- .../webserver/api/core/GetUserByIdAPI.java | 2 +- .../api/core/ListUsersByAccountInfoAPI.java | 2 +- .../webserver/api/core/UsersAPI.java | 2 +- .../ImportUserWithPasswordHashAPI.java | 3 +- .../api/emailpassword/SignInAPI.java | 4 +- .../webserver/api/emailpassword/UserAPI.java | 6 +- .../api/passwordless/ConsumeCodeAPI.java | 3 +- .../webserver/api/passwordless/UserAPI.java | 10 +- .../api/session/RefreshSessionAPI.java | 9 +- .../api/thirdparty/GetUsersByEmailAPI.java | 2 +- .../webserver/api/thirdparty/SignInUpAPI.java | 6 +- .../webserver/api/thirdparty/UserAPI.java | 5 +- .../io/supertokens/test/AuthRecipeTest.java | 6 + .../test/accountlinking/SessionTests.java | 40 ++++ .../test/authRecipe/UserPaginationTest.java | 66 ++++++ .../api/TestWithNonAuthRecipes.java | 12 +- .../test/session/SessionTest6.java | 202 +++++++++++++++++ .../api/RefreshSessionAPITest2_21.java | 2 +- .../session/api/RefreshSessionAPITest3_0.java | 207 ++++++++++++++++++ .../api/SessionRegenerateAPITest2_21.java | 1 + .../UserIdMappingStorageTest.java | 12 +- 33 files changed, 703 insertions(+), 73 deletions(-) create mode 100644 src/test/java/io/supertokens/test/session/SessionTest6.java create mode 100644 src/test/java/io/supertokens/test/session/api/RefreshSessionAPITest3_0.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 24905724f..d3fd7016f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [8.0.2] - 2024-03-21 + +- Fixes userIdMapping queries +- Fixes issue with session creation for users with userIdMapping and accounts linked +- Fixes active users tracking while linking accounts +- Adds a new required `useDynamicSigningKey` into the request body of `RefreshSessionAPI` + - This enables smooth switching between `useDynamicAccessTokenSigningKey` settings by allowing refresh calls to + change the signing key type of a session + ## [8.0.1] - 2024-03-11 - Making this version backward compatible. Breaking changes in `8.0.0` can now be ignored. diff --git a/build.gradle b/build.gradle index fa4132534..8300e7dd1 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "8.0.1" +version = "8.0.2" repositories { diff --git a/src/main/java/io/supertokens/ActiveUsers.java b/src/main/java/io/supertokens/ActiveUsers.java index 7ee3c5534..9ed68370e 100644 --- a/src/main/java/io/supertokens/ActiveUsers.java +++ b/src/main/java/io/supertokens/ActiveUsers.java @@ -1,5 +1,6 @@ package io.supertokens; +import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.StorageUtils; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; @@ -7,6 +8,7 @@ import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.sqlStorage.SQLStorage; import io.supertokens.storageLayer.StorageLayer; import org.jetbrains.annotations.TestOnly; @@ -37,6 +39,20 @@ public static int countUsersActiveSince(Main main, AppIdentifier appIdentifier, return StorageUtils.getActiveUsersStorage(storage).countUsersActiveSince(appIdentifier, time); } + public static void updateLastActiveAfterLinking(Main main, AppIdentifier appIdentifier, String primaryUserId, + String recipeUserId) + throws StorageQueryException, TenantOrAppNotFoundException, StorageTransactionLogicException { + ActiveUsersStorage activeUsersStorage = + StorageUtils.getActiveUsersStorage(StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main)); + + ((SQLStorage) activeUsersStorage).startTransaction(con -> { + activeUsersStorage.deleteUserActive_Transaction(con, appIdentifier, recipeUserId); + return null; + }); + + updateLastActive(appIdentifier, main, primaryUserId); + } + @TestOnly public static int countUsersActiveSince(Main main, long time) throws StorageQueryException, TenantOrAppNotFoundException { diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index e480f45f2..3f7428f44 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -925,6 +925,8 @@ private static void deleteNonAuthRecipeUser(TransactionConnection con, AppIdenti .deleteUserActive_Transaction(con, appIdentifier, userId); StorageUtils.getTOTPStorage(storage) .removeUser_Transaction(con, appIdentifier, userId); + StorageUtils.getActiveUsersStorage(storage) + .deleteUserActive_Transaction(con, appIdentifier, userId); } private static void deleteAuthRecipeUser(TransactionConnection con, diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 5d518faa9..4ba263eff 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -520,11 +520,11 @@ public SessionInfo getSessionInfo_Transaction(TenantIdentifier tenantIdentifier, @Override public void updateSessionInfo_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, String sessionHandle, String refreshTokenHash2, - long expiry) throws StorageQueryException { + long expiry, boolean useStaticKey) throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { SessionQueries.updateSessionInfo_Transaction(this, sqlCon, tenantIdentifier, sessionHandle, - refreshTokenHash2, expiry); + refreshTokenHash2, expiry, useStaticKey); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -2196,10 +2196,11 @@ public boolean updateOrDeleteExternalUserIdInfo(AppIdentifier appIdentifier, Str } @Override - public HashMap getUserIdMappingForSuperTokensIds(ArrayList userIds) + public HashMap getUserIdMappingForSuperTokensIds(AppIdentifier appIdentifier, + ArrayList userIds) throws StorageQueryException { try { - return UserIdMappingQueries.getUserIdMappingWithUserIds(this, userIds); + return UserIdMappingQueries.getUserIdMappingWithUserIds(this, appIdentifier, userIds); } catch (SQLException e) { throw new StorageQueryException(e); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java index 934391294..78684eeac 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java @@ -289,7 +289,7 @@ public static List isEmailVerified_transaction(Start start, Connection s // calculating the verified emails HashMap supertokensUserIdToExternalUserIdMap = UserIdMappingQueries.getUserIdMappingWithUserIds_Transaction(start, - sqlCon, supertokensUserIds); + sqlCon, appIdentifier, supertokensUserIds); HashMap externalUserIdToSupertokensUserIdMap = new HashMap<>(); List supertokensOrExternalUserIdsToQuery = new ArrayList<>(); @@ -357,7 +357,7 @@ public static List isEmailVerified(Start start, AppIdentifier appIdentif // We have external user id stored in the email verification table, so we need to fetch the mapped userids for // calculating the verified emails HashMap supertokensUserIdToExternalUserIdMap = UserIdMappingQueries.getUserIdMappingWithUserIds(start, - supertokensUserIds); + appIdentifier, supertokensUserIds); HashMap externalUserIdToSupertokensUserIdMap = new HashMap<>(); List supertokensOrExternalUserIdsToQuery = new ArrayList<>(); for (String userId : supertokensUserIds) { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java index 65fa18c1a..dbb23bbac 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java @@ -147,18 +147,19 @@ public static SessionInfo getSessionInfo_Transaction(Start start, Connection con public static void updateSessionInfo_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, String sessionHandle, - String refreshTokenHash2, long expiry) + String refreshTokenHash2, long expiry, boolean useStaticKey) throws SQLException, StorageQueryException { String QUERY = "UPDATE " + getConfig(start).getSessionInfoTable() - + " SET refresh_token_hash_2 = ?, expires_at = ?" + + " SET refresh_token_hash_2 = ?, expires_at = ?, use_static_key = ?" + " WHERE app_id = ? AND tenant_id = ? AND session_handle = ?"; update(con, QUERY, pst -> { pst.setString(1, refreshTokenHash2); pst.setLong(2, expiry); - pst.setString(3, tenantIdentifier.getAppId()); - pst.setString(4, tenantIdentifier.getTenantId()); - pst.setString(5, sessionHandle); + pst.setBoolean(3, useStaticKey); + pst.setString(4, tenantIdentifier.getAppId()); + pst.setString(5, tenantIdentifier.getTenantId()); + pst.setString(6, sessionHandle); }); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/UserIdMappingQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/UserIdMappingQueries.java index 579acc0b4..88c17a90f 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/UserIdMappingQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/UserIdMappingQueries.java @@ -136,7 +136,9 @@ public static UserIdMapping[] getUserIdMappingWithEitherSuperTokensUserIdOrExter } - public static HashMap getUserIdMappingWithUserIds(Start start, List userIds) + public static HashMap getUserIdMappingWithUserIds(Start start, + AppIdentifier appIdentifier, + List userIds) throws SQLException, StorageQueryException { if (userIds.size() == 0) { @@ -145,7 +147,8 @@ public static HashMap getUserIdMappingWithUserIds(Start start, L // No need to filter based on tenantId because the id list is already filtered for a tenant StringBuilder QUERY = new StringBuilder( - "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + " WHERE supertokens_user_id IN ("); + "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + " WHERE app_id = ? AND " + + "supertokens_user_id IN ("); for (int i = 0; i < userIds.size(); i++) { QUERY.append("?"); if (i != userIds.size() - 1) { @@ -155,9 +158,10 @@ public static HashMap getUserIdMappingWithUserIds(Start start, L } QUERY.append(")"); return execute(start, QUERY.toString(), pst -> { + pst.setString(1, appIdentifier.getAppId()); for (int i = 0; i < userIds.size(); i++) { - // i+1 cause this starts with 1 and not 0 - pst.setString(i + 1, userIds.get(i)); + // i+2 cause this starts with 1 and not 0, 1 is appId + pst.setString(i + 2, userIds.get(i)); } }, result -> { HashMap userIdMappings = new HashMap<>(); @@ -169,7 +173,9 @@ public static HashMap getUserIdMappingWithUserIds(Start start, L }); } - public static HashMap getUserIdMappingWithUserIds_Transaction(Start start, Connection sqlCon, List userIds) + public static HashMap getUserIdMappingWithUserIds_Transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + List userIds) throws SQLException, StorageQueryException { if (userIds.size() == 0) { @@ -178,7 +184,8 @@ public static HashMap getUserIdMappingWithUserIds_Transaction(St // No need to filter based on tenantId because the id list is already filtered for a tenant StringBuilder QUERY = new StringBuilder( - "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + " WHERE supertokens_user_id IN ("); + "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + " WHERE app_id = ? AND " + + "supertokens_user_id IN ("); for (int i = 0; i < userIds.size(); i++) { QUERY.append("?"); if (i != userIds.size() - 1) { @@ -188,9 +195,10 @@ public static HashMap getUserIdMappingWithUserIds_Transaction(St } QUERY.append(")"); return execute(sqlCon, QUERY.toString(), pst -> { + pst.setString(1, appIdentifier.getAppId()); for (int i = 0; i < userIds.size(); i++) { - // i+1 cause this starts with 1 and not 0 - pst.setString(i + 1, userIds.get(i)); + // i+2 cause this starts with 1 and not 0, 1 is appId + pst.setString(i + 2, userIds.get(i)); } }, result -> { HashMap userIdMappings = new HashMap<>(); diff --git a/src/main/java/io/supertokens/session/Session.java b/src/main/java/io/supertokens/session/Session.java index c1969bd46..d53110be6 100644 --- a/src/main/java/io/supertokens/session/Session.java +++ b/src/main/java/io/supertokens/session/Session.java @@ -49,6 +49,8 @@ import io.supertokens.session.jwt.JWT; import io.supertokens.session.refreshToken.RefreshToken; import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.Utils; import org.jetbrains.annotations.TestOnly; @@ -139,12 +141,29 @@ public static SessionInformationHolder createNewSession(TenantIdentifier tenantI } String primaryUserId = recipeUserId; - if (storage.getType().equals(STORAGE_TYPE.SQL)) { + + if (storage.getType() == STORAGE_TYPE.SQL) { + io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping.getUserIdMapping( + tenantIdentifier.toAppIdentifier(), storage, recipeUserId, UserIdType.EXTERNAL); + if (userIdMapping != null) { + recipeUserId = userIdMapping.superTokensUserId; + } + primaryUserId = StorageUtils.getAuthRecipeStorage(storage) .getPrimaryUserIdStrForUserId(tenantIdentifier.toAppIdentifier(), recipeUserId); if (primaryUserId == null) { primaryUserId = recipeUserId; } + + HashMap userIdMappings = UserIdMapping.getUserIdMappingForSuperTokensUserIds( + tenantIdentifier.toAppIdentifier(), storage, + new ArrayList<>(Arrays.asList(primaryUserId, recipeUserId))); + if (userIdMappings.containsKey(primaryUserId)) { + primaryUserId = userIdMappings.get(primaryUserId); + } + if (userIdMappings.containsKey(recipeUserId)) { + recipeUserId = userIdMappings.get(recipeUserId); + } } String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null; @@ -379,7 +398,7 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M accessToken.sessionHandle, Utils.hashSHA256(accessToken.refreshTokenHash1), System.currentTimeMillis() + - config.getRefreshTokenValidity()); + config.getRefreshTokenValidity(), sessionInfo.useStaticKey); } sessionStorage.commitTransaction(con); @@ -456,7 +475,7 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M Utils.hashSHA256(accessToken.refreshTokenHash1), System.currentTimeMillis() + Config.getConfig(tenantIdentifier, main) .getRefreshTokenValidity(), - sessionInfo.lastUpdatedSign); + sessionInfo.lastUpdatedSign, sessionInfo.useStaticKey); if (!success) { continue; } @@ -511,7 +530,7 @@ public static SessionInformationHolder refreshSession(Main main, @Nonnull String UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError { try { return refreshSession(new AppIdentifier(null, null), main, refreshToken, antiCsrfToken, - enableAntiCsrf, accessTokenVersion); + enableAntiCsrf, accessTokenVersion, null); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } @@ -520,7 +539,8 @@ public static SessionInformationHolder refreshSession(Main main, @Nonnull String public static SessionInformationHolder refreshSession(AppIdentifier appIdentifier, Main main, @Nonnull String refreshToken, @Nullable String antiCsrfToken, boolean enableAntiCsrf, - AccessToken.VERSION accessTokenVersion) + AccessToken.VERSION accessTokenVersion, + Boolean shouldUseStaticKey) throws StorageTransactionLogicException, UnauthorisedException, StorageQueryException, TokenTheftDetectedException, UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError, TenantOrAppNotFoundException { @@ -537,14 +557,15 @@ public static SessionInformationHolder refreshSession(AppIdentifier appIdentifie TenantIdentifier tenantIdentifier = refreshTokenInfo.tenantIdentifier; Storage storage = StorageLayer.getStorage(refreshTokenInfo.tenantIdentifier, main); return refreshSessionHelper( - tenantIdentifier, storage, main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion); + tenantIdentifier, storage, main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion, shouldUseStaticKey); } private static SessionInformationHolder refreshSessionHelper( TenantIdentifier tenantIdentifier, Storage storage, Main main, String refreshToken, RefreshToken.RefreshTokenInfo refreshTokenInfo, boolean enableAntiCsrf, - AccessToken.VERSION accessTokenVersion) + AccessToken.VERSION accessTokenVersion, + Boolean shouldUseStaticKey) throws StorageTransactionLogicException, UnauthorisedException, StorageQueryException, TokenTheftDetectedException, UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError, TenantOrAppNotFoundException { @@ -569,7 +590,16 @@ private static SessionInformationHolder refreshSessionHelper( throw new UnauthorisedException("Session missing in db or has expired"); } + boolean useStaticKey = shouldUseStaticKey != null ? shouldUseStaticKey : sessionInfo.useStaticKey; + if (sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(Utils.hashSHA256(refreshToken)))) { + if (useStaticKey != sessionInfo.useStaticKey) { + // We do not update anything except the static key status + sessionStorage.updateSessionInfo_Transaction(tenantIdentifier, con, sessionHandle, + sessionInfo.refreshTokenHash2, sessionInfo.expiry, + useStaticKey); + } + // at this point, the input refresh token is the parent one. sessionStorage.commitTransaction(con); @@ -583,7 +613,7 @@ private static SessionInformationHolder refreshSessionHelper( sessionInfo.recipeUserId, sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token), Utils.hashSHA256(refreshToken), sessionInfo.userDataInJWT, antiCsrfToken, - null, accessTokenVersion, sessionInfo.useStaticKey); + null, accessTokenVersion, useStaticKey); TokenInfo idRefreshToken = new TokenInfo(UUID.randomUUID().toString(), newRefreshToken.expiry, newRefreshToken.createdTime); @@ -603,13 +633,13 @@ private static SessionInformationHolder refreshSessionHelper( .equals(sessionInfo.refreshTokenHash2))) { sessionStorage.updateSessionInfo_Transaction(tenantIdentifier, con, sessionHandle, Utils.hashSHA256(Utils.hashSHA256(refreshToken)), - System.currentTimeMillis() + config.getRefreshTokenValidity()); + System.currentTimeMillis() + config.getRefreshTokenValidity(), useStaticKey); sessionStorage.commitTransaction(con); return refreshSessionHelper(tenantIdentifier, storage, main, refreshToken, refreshTokenInfo, enableAntiCsrf, - accessTokenVersion); + accessTokenVersion, useStaticKey); } sessionStorage.commitTransaction(con); @@ -658,7 +688,18 @@ private static SessionInformationHolder refreshSessionHelper( throw new UnauthorisedException("Session missing in db or has expired"); } + boolean useStaticKey = shouldUseStaticKey != null ? shouldUseStaticKey : sessionInfo.useStaticKey; + if (sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(Utils.hashSHA256(refreshToken)))) { + if (sessionInfo.useStaticKey != useStaticKey) { + // We do not update anything except the static key status + boolean success = sessionStorage.updateSessionInfo_Transaction(sessionHandle, + sessionInfo.refreshTokenHash2, sessionInfo.expiry, + sessionInfo.lastUpdatedSign, useStaticKey); + if (!success) { + continue; + } + } // at this point, the input refresh token is the parent one. String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null; @@ -669,7 +710,7 @@ private static SessionInformationHolder refreshSessionHelper( sessionHandle, sessionInfo.recipeUserId, sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token), Utils.hashSHA256(refreshToken), sessionInfo.userDataInJWT, antiCsrfToken, - null, accessTokenVersion, sessionInfo.useStaticKey); + null, accessTokenVersion, useStaticKey); TokenInfo idRefreshToken = new TokenInfo(UUID.randomUUID().toString(), newRefreshToken.expiry, newRefreshToken.createdTime); @@ -691,13 +732,13 @@ private static SessionInformationHolder refreshSessionHelper( Utils.hashSHA256(Utils.hashSHA256(refreshToken)), System.currentTimeMillis() + Config.getConfig(tenantIdentifier, main).getRefreshTokenValidity(), - sessionInfo.lastUpdatedSign); + sessionInfo.lastUpdatedSign, useStaticKey); if (!success) { continue; } return refreshSessionHelper(tenantIdentifier, storage, main, refreshToken, refreshTokenInfo, enableAntiCsrf, - accessTokenVersion); + accessTokenVersion, shouldUseStaticKey); } throw new TokenTheftDetectedException(sessionHandle, sessionInfo.recipeUserId, sessionInfo.userId); diff --git a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java index 274e08efc..cfb05adfd 100644 --- a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java +++ b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java @@ -338,11 +338,12 @@ public static boolean updateOrDeleteExternalUserIdInfo(Main main, } public static HashMap getUserIdMappingForSuperTokensUserIds( + AppIdentifier appIdentifier, Storage storage, ArrayList userIds) throws StorageQueryException { // userIds are already filtered for a tenant - return StorageUtils.getUserIdMappingStorage(storage).getUserIdMappingForSuperTokensIds(userIds); + return StorageUtils.getUserIdMappingStorage(storage).getUserIdMappingForSuperTokensIds(appIdentifier, userIds); } @TestOnly @@ -350,7 +351,7 @@ public static HashMap getUserIdMappingForSuperTokensUserIds(Main ArrayList userIds) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); - return getUserIdMappingForSuperTokensUserIds(storage, userIds); + return getUserIdMappingForSuperTokensUserIds(new AppIdentifier(null, null), storage, userIds); } public static List findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( @@ -422,7 +423,8 @@ public static List findNonAuthStoragesWhereUserIdIsUsedOrAssertIfUsed( return result; } - public static void populateExternalUserIdForUsers(Storage storage, AuthRecipeUserInfo[] users) + public static void populateExternalUserIdForUsers(AppIdentifier appIdentifier, Storage storage, + AuthRecipeUserInfo[] users) throws StorageQueryException { Set userIds = new HashSet<>(); @@ -435,7 +437,8 @@ public static void populateExternalUserIdForUsers(Storage storage, AuthRecipeUse } ArrayList userIdsList = new ArrayList<>(userIds); userIdsList.addAll(userIds); - HashMap userIdMappings = getUserIdMappingForSuperTokensUserIds(storage, userIdsList); + HashMap userIdMappings = getUserIdMappingForSuperTokensUserIds(appIdentifier, storage, + userIdsList); for (AuthRecipeUserInfo user : users) { user.setExternalUserId(userIdMappings.get(user.getSupertokensUserId())); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java index 75e416eb9..4c5212708 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java @@ -130,7 +130,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO try { JsonObject response = new JsonObject(); response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); - UserIdMapping.populateExternalUserIdForUsers(recipeUserIdStorage, + UserIdMapping.populateExternalUserIdForUsers(appIdentifier, recipeUserIdStorage, new AuthRecipeUserInfo[]{e.recipeUser}); response.addProperty("primaryUserId", e.recipeUser.getSupertokensOrExternalUserId()); response.addProperty("description", e.getMessage()); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java index 0fa6fbb7f..80caf2ff3 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java @@ -17,6 +17,7 @@ package io.supertokens.webserver.api.accountlinking; import com.google.gson.JsonObject; +import io.supertokens.ActiveUsers; import io.supertokens.Main; import io.supertokens.StorageAndUserIdMapping; import io.supertokens.authRecipe.AuthRecipe; @@ -105,11 +106,22 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I appIdentifier, primaryUserIdStorage, recipeUserId, primaryUserId); - UserIdMapping.populateExternalUserIdForUsers(primaryUserIdStorage, new AuthRecipeUserInfo[]{linkAccountsResult.user}); + UserIdMapping.populateExternalUserIdForUsers(appIdentifier, primaryUserIdStorage, + new AuthRecipeUserInfo[]{linkAccountsResult.user}); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); response.addProperty("accountsAlreadyLinked", linkAccountsResult.wasAlreadyLinked); response.add("user", linkAccountsResult.user.toJson()); + + if (!linkAccountsResult.wasAlreadyLinked) { + try { + ActiveUsers.updateLastActiveAfterLinking( + main, appIdentifier, primaryUserId, recipeUserId); + } catch (Exception e) { + // ignore + } + } + super.sendJsonResponse(200, response, resp); } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException | BadPermissionException e) { @@ -137,7 +149,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { JsonObject response = new JsonObject(); response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); - UserIdMapping.populateExternalUserIdForUsers(recipeUserIdStorage, new AuthRecipeUserInfo[]{e.recipeUser}); + UserIdMapping.populateExternalUserIdForUsers(appIdentifier, recipeUserIdStorage, + new AuthRecipeUserInfo[]{e.recipeUser}); response.addProperty("primaryUserId", e.recipeUser.getSupertokensOrExternalUserId()); response.addProperty("description", e.getMessage()); response.add("user", e.recipeUser.toJson()); diff --git a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java index a09155701..37d956ce5 100644 --- a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java @@ -71,7 +71,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // if a userIdMapping exists, set the userId in the response to the externalUserId if (user != null) { UserIdMapping.populateExternalUserIdForUsers( - storageAndUserIdMapping.storage, new AuthRecipeUserInfo[]{user}); + appIdentifier, storageAndUserIdMapping.storage, new AuthRecipeUserInfo[]{user}); } } catch (UnknownUserIdException e) { diff --git a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java index 4c002bed2..00dd0123f 100644 --- a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java @@ -78,7 +78,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo( tenantIdentifier, storage, doUnionOfAccountInfo, email, phoneNumber, thirdPartyId, thirdPartyUserId); - UserIdMapping.populateExternalUserIdForUsers(storage, users); + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifier.toAppIdentifier(), storage, users); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index 2959ca24a..46b45ea30 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -170,7 +170,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO limit, timeJoinedOrder, paginationToken, recipeIdsEnumBuilder.build().toArray(RECIPE_ID[]::new), searchTags); - UserIdMapping.populateExternalUserIdForUsers(storage, users.users); + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifier.toAppIdentifier(), storage, users.users); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java index 3d05fd5ff..30857d18d 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java @@ -100,7 +100,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I EmailPassword.ImportUserResponse importUserResponse = EmailPassword.importUserWithPasswordHash( tenantIdentifier, storage, main, email, passwordHash, passwordHashingAlgorithm); - UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{importUserResponse.user}); + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifier.toAppIdentifier(), storage, + new AuthRecipeUserInfo[]{importUserResponse.user}); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); JsonObject userJson = diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java index f966180f0..7e50a7171 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java @@ -78,8 +78,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { AuthRecipeUserInfo user = EmailPassword.signIn(tenantIdentifier, storage, super.main, normalisedEmail, password); - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, - new AuthRecipeUserInfo[]{user}); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers( + tenantIdentifier.toAppIdentifier(), storage, new AuthRecipeUserInfo[]{user}); ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, user.getSupertokensUserId()); // use the internal user id diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index f7b67e729..e191fea15 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -95,7 +95,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO appIdentifier, storageAndUserIdMapping.storage, userId); if (user != null) { - UserIdMapping.populateExternalUserIdForUsers(storageAndUserIdMapping.storage, + UserIdMapping.populateExternalUserIdForUsers( + appIdentifier, storageAndUserIdMapping.storage, new AuthRecipeUserInfo[]{user}); } @@ -109,7 +110,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // if a userIdMapping exists, set the userId in the response to the externalUserId if (user != null) { - UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{user}); + UserIdMapping.populateExternalUserIdForUsers(appIdentifier, storage, + new AuthRecipeUserInfo[]{user}); } } } catch (UnknownUserIdException e) { diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java index a1aa41ee6..56b1ff8fa 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -92,7 +92,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I userInputCode, linkCode, // From CDI version 4.0 onwards, the email verification will be set getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)); - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{consumeCodeResponse.user}); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers( + tenantIdentifier.toAppIdentifier(), storage, new AuthRecipeUserInfo[]{consumeCodeResponse.user}); ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, consumeCodeResponse.user.getSupertokensUserId()); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index 430743262..d9d47d389 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -92,7 +92,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // if the userIdMapping exists set the userId in the response to the externalUserId if (user != null) { io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers( - storageAndUserIdMapping.storage, new AuthRecipeUserInfo[]{user}); + appIdentifier, storageAndUserIdMapping.storage, new AuthRecipeUserInfo[]{user}); } } catch (UnknownUserIdException e) { user = null; @@ -103,8 +103,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO email = Utils.normaliseEmail(email); user = Passwordless.getUserByEmail(tenantIdentifier, storage, email); if (user != null) { - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, - new AuthRecipeUserInfo[]{user}); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers( + tenantIdentifier.toAppIdentifier(), storage, new AuthRecipeUserInfo[]{user}); } } else { TenantIdentifier tenantIdentifier = getTenantIdentifier(req); @@ -112,8 +112,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user = Passwordless.getUserByPhoneNumber(tenantIdentifier, storage, phoneNumber); if (user != null) { - io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(storage, - new AuthRecipeUserInfo[]{user}); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers( + tenantIdentifier.toAppIdentifier(), storage, new AuthRecipeUserInfo[]{user}); } } diff --git a/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java index dca193196..25a8e9ed4 100644 --- a/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java @@ -63,15 +63,17 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific, but session is updated based on tenantId obtained from the refreshToken + SemVer version = super.getVersionFromRequest(req); + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String refreshToken = InputParser.parseStringOrThrowError(input, "refreshToken", false); String antiCsrfToken = InputParser.parseStringOrThrowError(input, "antiCsrfToken", true); Boolean enableAntiCsrf = InputParser.parseBooleanOrThrowError(input, "enableAntiCsrf", false); + Boolean useDynamicSigningKey = version.greaterThanOrEqualTo(SemVer.v3_0) ? + InputParser.parseBooleanOrThrowError(input, "useDynamicSigningKey", true) : null; assert enableAntiCsrf != null; assert refreshToken != null; - - SemVer version = super.getVersionFromRequest(req); TenantIdentifier tenantIdentifierForLogging = null; try { tenantIdentifierForLogging = getTenantIdentifier(req); @@ -85,7 +87,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I SessionInformationHolder sessionInfo = Session.refreshSession(appIdentifier, main, refreshToken, antiCsrfToken, - enableAntiCsrf, accessTokenVersion); + enableAntiCsrf, accessTokenVersion, + useDynamicSigningKey == null ? null : Boolean.FALSE.equals(useDynamicSigningKey)); TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), sessionInfo.session.tenantId); Storage storage = StorageLayer.getStorage(tenantIdentifier, main); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java index 66fcc8758..45ab7ba41 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java @@ -61,7 +61,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO String email = InputParser.getQueryParamOrThrowError(req, "email", false); email = Utils.normaliseEmail(email); AuthRecipeUserInfo[] users = ThirdParty.getUsersByEmail(tenantIdentifier, storage, email); - UserIdMapping.populateExternalUserIdForUsers(storage, users); + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifier.toAppIdentifier(), storage, users); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index 42b16c7f9..52470f459 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -81,7 +81,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I ThirdParty.SignInUpResponse response = ThirdParty.signInUp2_7( tenantIdentifier, storage, thirdPartyId, thirdPartyUserId, email, isEmailVerified); - UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{response.user}); + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifier. toAppIdentifier(), storage, + new AuthRecipeUserInfo[]{response.user}); ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, response.user.getSupertokensUserId()); @@ -142,7 +143,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I ThirdParty.SignInUpResponse response = ThirdParty.signInUp( tenantIdentifier, storage, super.main, thirdPartyId, thirdPartyUserId, email, isEmailVerified); - UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{response.user}); + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifier.toAppIdentifier(), storage, + new AuthRecipeUserInfo[]{response.user}); ActiveUsers.updateLastActive(tenantIdentifier.toAppIdentifier(), main, response.user.getSupertokensUserId()); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java index f1089b011..1d0ec397c 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java @@ -91,7 +91,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user = ThirdParty.getUser(appIdentifier, storageAndUserIdMapping.storage, userId); if (user != null) { - UserIdMapping.populateExternalUserIdForUsers(storageAndUserIdMapping.storage, + UserIdMapping.populateExternalUserIdForUsers(appIdentifier, storageAndUserIdMapping.storage, new AuthRecipeUserInfo[]{user}); } } catch (UnknownUserIdException e) { @@ -103,7 +103,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user = ThirdParty.getUser(tenantIdentifier, storage, thirdPartyId, thirdPartyUserId); if (user != null) { - UserIdMapping.populateExternalUserIdForUsers(storage, new AuthRecipeUserInfo[]{user}); + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifier.toAppIdentifier(), storage, + new AuthRecipeUserInfo[]{user}); } } diff --git a/src/test/java/io/supertokens/test/AuthRecipeTest.java b/src/test/java/io/supertokens/test/AuthRecipeTest.java index d941f81cc..11bbbb65a 100644 --- a/src/test/java/io/supertokens/test/AuthRecipeTest.java +++ b/src/test/java/io/supertokens/test/AuthRecipeTest.java @@ -32,6 +32,7 @@ import io.supertokens.storageLayer.StorageLayer; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.usermetadata.UserMetadata; +import io.supertokens.version.Version; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; @@ -487,6 +488,8 @@ public void randomPaginationTest() throws Exception { fail(); } + boolean isMySQL = Version.getVersion(process.getProcess()).getPluginName().equals("mysql"); + for (int limit : limits) { // now we paginate in asc order @@ -496,6 +499,9 @@ public void randomPaginationTest() throws Exception { if (o1.timeJoined != o2.timeJoined) { return (int) (o1.timeJoined - o2.timeJoined); } + if (isMySQL) { + return o1.getSupertokensUserId().compareTo(o2.getSupertokensUserId()); + } return o2.getSupertokensUserId().compareTo(o1.getSupertokensUserId()); }); diff --git a/src/test/java/io/supertokens/test/accountlinking/SessionTests.java b/src/test/java/io/supertokens/test/accountlinking/SessionTests.java index c84b8aac1..f24a64044 100644 --- a/src/test/java/io/supertokens/test/accountlinking/SessionTests.java +++ b/src/test/java/io/supertokens/test/accountlinking/SessionTests.java @@ -617,4 +617,44 @@ public void testRevokeSessionsForUserWithAndWithoutIncludingAllLinkedAccounts() process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void testCreateSessionWithUserIdMappedForRecipeUser() 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 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + UserIdMapping.createUserIdMapping(process.getProcess(), user1.getSupertokensUserId(), "extid1", null, false); + UserIdMapping.createUserIdMapping(process.getProcess(), user2.getSupertokensUserId(), "extid2", null, false); + + SessionInformationHolder session1 = Session.createNewSession(process.getProcess(), user1.getSupertokensUserId(), new JsonObject(), new JsonObject()); + SessionInformationHolder session2 = Session.createNewSession(process.getProcess(), user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); + SessionInformationHolder session3 = Session.createNewSession(process.getProcess(), "extid1", new JsonObject(), new JsonObject()); + SessionInformationHolder session4 = Session.createNewSession(process.getProcess(), "extid2", new JsonObject(), new JsonObject()); + + assertEquals("extid1", session1.session.userId); + assertEquals("extid1", session1.session.recipeUserId); + assertEquals("extid1", session2.session.userId); + assertEquals("extid2", session2.session.recipeUserId); + assertEquals("extid1", session3.session.userId); + assertEquals("extid1", session3.session.recipeUserId); + assertEquals("extid1", session4.session.userId); + assertEquals("extid2", session4.session.recipeUserId); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java index ce47e885d..9fe55d714 100644 --- a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java @@ -20,6 +20,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.UserPaginationContainer; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.featureflag.EE_FEATURES; @@ -33,6 +35,7 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -40,6 +43,7 @@ import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -343,4 +347,66 @@ public void testUserPaginationWorksCorrectlyForEachTenant() throws Exception { } } } + + @Test + public void testUserPaginationWithSameTimeJoined() throws Exception { + if (StorageLayer.getBaseStorage(process.main).getType() != STORAGE_TYPE.SQL) { + return; + } + + ThirdPartySQLStorage storage = (ThirdPartySQLStorage) StorageLayer.getBaseStorage(process.getProcess()); + + Set userIds = new HashSet<>(); + + long timeJoined = System.currentTimeMillis(); + for (int i = 0; i < 100; i++) { + String userId = io.supertokens.utils.Utils.getUUID(); + storage.signUp(TenantIdentifier.BASE_TENANT, userId, "test"+i+"@example.com", new LoginMethod.ThirdParty("google", userId), timeJoined); + userIds.add(userId); + } + + // Test ascending + { + Set paginationUserIds = new HashSet<>(); + UserPaginationContainer usersRes = AuthRecipe.getUsers(process.getProcess(), 10, + "ASC", null, null, null); + + while (true) { + for (AuthRecipeUserInfo user : usersRes.users) { + paginationUserIds.add(user.getSupertokensUserId()); + } + + if (usersRes.nextPaginationToken == null) { + break; + } + usersRes = AuthRecipe.getUsers(process.getProcess(), 10, + "ASC", usersRes.nextPaginationToken, null, null); + } + + assertEquals(userIds.size(), paginationUserIds.size()); + assertEquals(userIds, paginationUserIds); + } + + // Test descending + { + Set paginationUserIds = new HashSet<>(); + UserPaginationContainer usersRes = AuthRecipe.getUsers(process.getProcess(), 10, + "DESC", null, null, null); + + while (true) { + for (AuthRecipeUserInfo user : usersRes.users) { + paginationUserIds.add(user.getSupertokensUserId()); + } + + if (usersRes.nextPaginationToken == null) { + break; + } + usersRes = AuthRecipe.getUsers(process.getProcess(), 10, + "DESC", usersRes.nextPaginationToken, null, null); + } + + assertEquals(userIds.size(), paginationUserIds.size()); + assertEquals(userIds, paginationUserIds); + } + } } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java b/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java index d755d679a..005ca87a2 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestWithNonAuthRecipes.java @@ -114,8 +114,8 @@ public void testThatUserMetadataIsSavedInTheStorageWhereUserExists() throws Exce AuthRecipeUserInfo user1 = EmailPassword.signUp(t0, t0Storage, process.getProcess(), "test@example.com", "password123"); AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", "password123"); - UserIdMapping.populateExternalUserIdForUsers(t0Storage, new AuthRecipeUserInfo[]{user1}); - UserIdMapping.populateExternalUserIdForUsers(t1Storage, new AuthRecipeUserInfo[]{user2}); + UserIdMapping.populateExternalUserIdForUsers(t0.toAppIdentifier(), t0Storage, new AuthRecipeUserInfo[]{user1}); + UserIdMapping.populateExternalUserIdForUsers(t1.toAppIdentifier(), t1Storage, new AuthRecipeUserInfo[]{user2}); // Check that get user by ID works fine JsonObject jsonUser1 = TestMultitenancyAPIHelper.getUserById(t0, user1.getSupertokensUserId(), process.getProcess()); @@ -191,8 +191,8 @@ public void testThatRoleIsStoredInPublicTenantAndUserRoleMappingInTheUserTenantS AuthRecipeUserInfo user1 = EmailPassword.signUp(t0, t0Storage, process.getProcess(), "test@example.com", "password123"); AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", "password123"); - UserIdMapping.populateExternalUserIdForUsers(t0Storage, new AuthRecipeUserInfo[]{user1}); - UserIdMapping.populateExternalUserIdForUsers(t1Storage, new AuthRecipeUserInfo[]{user2}); + UserIdMapping.populateExternalUserIdForUsers(t0.toAppIdentifier(), t0Storage, new AuthRecipeUserInfo[]{user1}); + UserIdMapping.populateExternalUserIdForUsers(t1.toAppIdentifier(), t1Storage, new AuthRecipeUserInfo[]{user2}); { // Check that get user by ID works fine @@ -276,8 +276,8 @@ public void testEmailVerificationWithUsersOnDifferentTenantStorages() throws Exc AuthRecipeUserInfo user1 = EmailPassword.signUp(t0, t0Storage, process.getProcess(), "test@example.com", "password123"); AuthRecipeUserInfo user2 = EmailPassword.signUp(t1, t1Storage, process.getProcess(), "test@example.com", "password123"); - UserIdMapping.populateExternalUserIdForUsers(t0Storage, new AuthRecipeUserInfo[]{user1}); - UserIdMapping.populateExternalUserIdForUsers(t1Storage, new AuthRecipeUserInfo[]{user2}); + UserIdMapping.populateExternalUserIdForUsers(t0.toAppIdentifier(), t0Storage, new AuthRecipeUserInfo[]{user1}); + UserIdMapping.populateExternalUserIdForUsers(t1.toAppIdentifier(), t1Storage, new AuthRecipeUserInfo[]{user2}); // Check that get user by ID works fine JsonObject jsonUser1 = TestMultitenancyAPIHelper.getUserById(t0, user1.getSupertokensUserId(), process.getProcess()); diff --git a/src/test/java/io/supertokens/test/session/SessionTest6.java b/src/test/java/io/supertokens/test/session/SessionTest6.java new file mode 100644 index 000000000..7b6bc9945 --- /dev/null +++ b/src/test/java/io/supertokens/test/session/SessionTest6.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2021, 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.session; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.exceptions.TryRefreshTokenException; +import io.supertokens.exceptions.UnauthorisedException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.session.SessionStorage; +import io.supertokens.session.Session; +import io.supertokens.session.accessToken.AccessToken; +import io.supertokens.session.info.SessionInformationHolder; +import io.supertokens.session.jwt.JWT; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import org.junit.*; +import org.junit.rules.TestRule; + +import static junit.framework.TestCase.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +public class SessionTest6 { + + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void createRefreshSwitchVerify() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + SessionInformationHolder sessionInfo = Session.createNewSession(process.getProcess(), userId, userDataInJWT, + userDataInDatabase, false, AccessToken.getLatestVersion(), false); + checkIfUsingStaticKey(sessionInfo, false); + + sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token, + sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), true); + assert sessionInfo.refreshToken != null; + assert sessionInfo.accessToken != null; + + checkIfUsingStaticKey(sessionInfo, true); + + SessionInformationHolder verifiedSession = Session.getSession(process.getProcess(), sessionInfo.accessToken.token, + sessionInfo.antiCsrfToken, false, true, false); + + checkIfUsingStaticKey(verifiedSession, true); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + @Test + public void createRefreshSwitchRegen() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + SessionInformationHolder sessionInfo = Session.createNewSession(process.getProcess(), userId, userDataInJWT, + userDataInDatabase, false, AccessToken.getLatestVersion(), false); + checkIfUsingStaticKey(sessionInfo, false); + + sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token, + sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), true); + assert sessionInfo.refreshToken != null; + assert sessionInfo.accessToken != null; + checkIfUsingStaticKey(sessionInfo, true); + + SessionInformationHolder newSessionInfo = Session.regenerateToken(process.getProcess(), + sessionInfo.accessToken.token, userDataInJWT); + checkIfUsingStaticKey(newSessionInfo, true); + + SessionInformationHolder getSessionResponse = Session.getSession(process.getProcess(), + newSessionInfo.accessToken.token, sessionInfo.antiCsrfToken, false, true, false); + checkIfUsingStaticKey(getSessionResponse, true); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void createRefreshRefreshSwitchVerify() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + SessionInformationHolder sessionInfo = Session.createNewSession(process.getProcess(), userId, userDataInJWT, + userDataInDatabase, false, AccessToken.getLatestVersion(), false); + checkIfUsingStaticKey(sessionInfo, false); + + sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token, + sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), false); + + sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token, + sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), true); + assert sessionInfo.refreshToken != null; + assert sessionInfo.accessToken != null; + + checkIfUsingStaticKey(sessionInfo, true); + + SessionInformationHolder verifiedSession = Session.getSession(process.getProcess(), sessionInfo.accessToken.token, + sessionInfo.antiCsrfToken, false, true, false); + + checkIfUsingStaticKey(verifiedSession, true); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + @Test + public void createRefreshRefreshSwitchRegen() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + SessionInformationHolder sessionInfo = Session.createNewSession(process.getProcess(), userId, userDataInJWT, + userDataInDatabase, false, AccessToken.getLatestVersion(), false); + checkIfUsingStaticKey(sessionInfo, false); + + sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token, + sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), false); + + sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token, + sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), true); + assert sessionInfo.refreshToken != null; + assert sessionInfo.accessToken != null; + checkIfUsingStaticKey(sessionInfo, true); + + SessionInformationHolder newSessionInfo = Session.regenerateToken(process.getProcess(), + sessionInfo.accessToken.token, userDataInJWT); + checkIfUsingStaticKey(newSessionInfo, true); + + SessionInformationHolder getSessionResponse = Session.getSession(process.getProcess(), + newSessionInfo.accessToken.token, sessionInfo.antiCsrfToken, false, true, false); + checkIfUsingStaticKey(getSessionResponse, true); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + private static void checkIfUsingStaticKey(SessionInformationHolder info, boolean shouldBeStatic) throws JWT.JWTException { + assert info.accessToken != null; + JWT.JWTPreParseInfo tokenInfo = JWT.preParseJWTInfo(info.accessToken.token); + assert tokenInfo.kid != null; + if (shouldBeStatic) { + assert tokenInfo.kid.startsWith("s-"); + } else { + assert tokenInfo.kid.startsWith("d-"); + } + } + +} \ No newline at end of file diff --git a/src/test/java/io/supertokens/test/session/api/RefreshSessionAPITest2_21.java b/src/test/java/io/supertokens/test/session/api/RefreshSessionAPITest2_21.java index 07738e235..9be3bbcd7 100644 --- a/src/test/java/io/supertokens/test/session/api/RefreshSessionAPITest2_21.java +++ b/src/test/java/io/supertokens/test/session/api/RefreshSessionAPITest2_21.java @@ -76,7 +76,7 @@ public void checkRefreshWithProtectedFieldInPayload() throws Exception { JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null, - Utils.getCdiVersionStringLatestForTests(), "session"); + SemVer.v2_21.get(), "session"); assertEquals(response.entrySet().size(), 2); assertEquals(response.get("status").getAsString(), "UNAUTHORISED"); diff --git a/src/test/java/io/supertokens/test/session/api/RefreshSessionAPITest3_0.java b/src/test/java/io/supertokens/test/session/api/RefreshSessionAPITest3_0.java new file mode 100644 index 000000000..4f51fc405 --- /dev/null +++ b/src/test/java/io/supertokens/test/session/api/RefreshSessionAPITest3_0.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2021, 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.session.api; + +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.session.jwt.JWT; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertNotNull; + +public class RefreshSessionAPITest3_0 { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void successOutputWithValidRefreshTokenTest() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.add("nullProp", JsonNull.INSTANCE); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("enableAntiCsrf", false); + + JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v2_7.get(), + "session"); + assertEquals(sessionInfo.get("status").getAsString(), "OK"); + + JsonObject sessionRefreshBody = new JsonObject(); + + sessionRefreshBody.addProperty("refreshToken", + sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString()); + sessionRefreshBody.addProperty("enableAntiCsrf", false); + + JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null, + SemVer.v3_0.get(), "session"); + + checkRefreshSessionResponse(sessionRefreshResponse, process, userId, userDataInJWT, false, false); + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + + } + + @Test + public void successOutputUpgradeWithNonStaticKeySessionTest() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.add("nullProp", JsonNull.INSTANCE); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("enableAntiCsrf", false); + + JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v2_7.get(), + "session"); + assertEquals(sessionInfo.get("status").getAsString(), "OK"); + + JsonObject sessionRefreshBody = new JsonObject(); + + sessionRefreshBody.addProperty("refreshToken", + sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString()); + sessionRefreshBody.addProperty("enableAntiCsrf", false); + sessionRefreshBody.addProperty("useDynamicSigningKey", true); + + JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null, + SemVer.v3_0.get(), "session"); + + checkRefreshSessionResponse(sessionRefreshResponse, process, userId, userDataInJWT, false, false); + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void successOutputUpgradeWithStaticKeySessionTest() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + String userId = "userId"; + JsonObject userDataInJWT = new JsonObject(); + userDataInJWT.add("nullProp", JsonNull.INSTANCE); + userDataInJWT.addProperty("key", "value"); + JsonObject userDataInDatabase = new JsonObject(); + userDataInDatabase.addProperty("key", "value"); + + JsonObject request = new JsonObject(); + request.addProperty("userId", userId); + request.add("userDataInJWT", userDataInJWT); + request.add("userDataInDatabase", userDataInDatabase); + request.addProperty("enableAntiCsrf", false); + + JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v2_7.get(), + "session"); + assertEquals(sessionInfo.get("status").getAsString(), "OK"); + + JsonObject sessionRefreshBody = new JsonObject(); + + sessionRefreshBody.addProperty("refreshToken", + sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString()); + sessionRefreshBody.addProperty("enableAntiCsrf", false); + sessionRefreshBody.addProperty("useDynamicSigningKey", false); + + JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null, + SemVer.v3_0.get(), "session"); + + checkRefreshSessionResponse(sessionRefreshResponse, process, userId, userDataInJWT, false, true); + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + private static void checkRefreshSessionResponse(JsonObject response, TestingProcessManager.TestingProcess process, + String userId, JsonObject userDataInJWT, boolean hasAntiCsrf, boolean useStaticKey) throws + JWT.JWTException { + + assertNotNull(response.get("session").getAsJsonObject().get("handle").getAsString()); + assertEquals(response.get("session").getAsJsonObject().get("userId").getAsString(), userId); + assertEquals(response.get("session").getAsJsonObject().get("tenantId").getAsString(), "public"); + assertEquals(response.get("session").getAsJsonObject().get("userDataInJWT").getAsJsonObject().toString(), + userDataInJWT.toString()); + assertEquals(response.get("session").getAsJsonObject().entrySet().size(), 4); + + assertTrue(response.get("accessToken").getAsJsonObject().has("token")); + assertTrue(response.get("accessToken").getAsJsonObject().has("expiry")); + assertTrue(response.get("accessToken").getAsJsonObject().has("createdTime")); + assertEquals(response.get("accessToken").getAsJsonObject().entrySet().size(), 3); + + JWT.JWTPreParseInfo tokenInfo = JWT.preParseJWTInfo(response.get("accessToken").getAsJsonObject().get("token").getAsString()); + + if (useStaticKey) { + assert(tokenInfo.kid.startsWith("s-")); + } else { + assert(tokenInfo.kid.startsWith("d-")); + } + + assertTrue(response.get("refreshToken").getAsJsonObject().has("token")); + assertTrue(response.get("refreshToken").getAsJsonObject().has("expiry")); + assertTrue(response.get("refreshToken").getAsJsonObject().has("createdTime")); + assertEquals(response.get("refreshToken").getAsJsonObject().entrySet().size(), 3); + + assertEquals(response.has("antiCsrfToken"), hasAntiCsrf); + + assertEquals(response.entrySet().size(), hasAntiCsrf ? 5 : 4); + } +} \ No newline at end of file diff --git a/src/test/java/io/supertokens/test/session/api/SessionRegenerateAPITest2_21.java b/src/test/java/io/supertokens/test/session/api/SessionRegenerateAPITest2_21.java index 05e2f97a3..488c3f8f0 100644 --- a/src/test/java/io/supertokens/test/session/api/SessionRegenerateAPITest2_21.java +++ b/src/test/java/io/supertokens/test/session/api/SessionRegenerateAPITest2_21.java @@ -107,6 +107,7 @@ public void testCallRegenerateAPIWithProtectedFieldInJWTV3Token() throws Excepti sessionRefreshBody.addProperty("refreshToken", sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString()); sessionRefreshBody.addProperty("enableAntiCsrf", false); + sessionRefreshBody.addProperty("useDynamicSigningKey", true); JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null, diff --git a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java index 26e5bf129..ca7e6ce1b 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java @@ -583,7 +583,8 @@ public void createUsersMapTheirIdsCheckRetrieveUseIdMappingsWithListOfUserIds() storage.createUserIdMapping(new AppIdentifier(null, null), superTokensUserId, externalUserId, null); } - HashMap response = storage.getUserIdMappingForSuperTokensIds(superTokensUserIdList); + HashMap response = storage.getUserIdMappingForSuperTokensIds( + new AppIdentifier(null, null), superTokensUserIdList); assertEquals(AuthRecipe.USER_PAGINATION_LIMIT, response.size()); for (int i = 0; i < response.size(); i++) { assertEquals(externalUserIdList.get(i), response.get(superTokensUserIdList.get(i))); @@ -606,7 +607,8 @@ public void testCallingGetUserIdMappingForSuperTokensIdsWithEmptyList() throws E UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); ArrayList emptyList = new ArrayList<>(); - HashMap response = storage.getUserIdMappingForSuperTokensIds(emptyList); + HashMap response = storage.getUserIdMappingForSuperTokensIds( + new AppIdentifier(null, null), emptyList); assertEquals(0, response.size()); process.kill(); @@ -631,7 +633,8 @@ public void testCallingGetUserIdMappingForSuperTokensIdsWhenNoMappingExists() th superTokensUserIdList.add(userInfo.getSupertokensUserId()); } - HashMap userIdMapping = storage.getUserIdMappingForSuperTokensIds(superTokensUserIdList); + HashMap userIdMapping = storage.getUserIdMappingForSuperTokensIds( + new AppIdentifier(null, null), superTokensUserIdList); assertEquals(0, userIdMapping.size()); process.kill(); @@ -668,7 +671,8 @@ public void create10UsersAndMap5UsersIds() throws Exception { } // retrieve UserIDMapping - HashMap response = storage.getUserIdMappingForSuperTokensIds(superTokensUserIdList); + HashMap response = storage.getUserIdMappingForSuperTokensIds( + new AppIdentifier(null, null), superTokensUserIdList); assertEquals(5, response.size()); // check that the last 5 users have their ids mapped