From 6a881b5d8c4b618d2f8798ee889df5c9a61da9e2 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 21 Mar 2024 17:51:00 +0530 Subject: [PATCH] fix: backports to core 7.0 --- CHANGELOG.md | 8 + build.gradle | 2 +- src/main/java/io/supertokens/ActiveUsers.java | 15 ++ .../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 | 6 +- .../api/accountlinking/LinkAccountsAPI.java | 10 + .../api/session/RefreshSessionAPI.java | 8 +- .../io/supertokens/test/AuthRecipeTest.java | 6 + .../test/accountlinking/SessionTests.java | 40 ++++ .../test/authRecipe/UserPaginationTest.java | 66 ++++++ .../test/session/SessionTest6.java | 202 +++++++++++++++++ .../api/RefreshSessionAPITest2_21.java | 2 +- .../session/api/RefreshSessionAPITest3_0.java | 207 ++++++++++++++++++ .../api/SessionRegenerateAPITest2_21.java | 1 + .../UserIdMappingStorageTest.java | 12 +- 19 files changed, 658 insertions(+), 42 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 c19515d0a..2ade550ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ 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). +## [7.0.19] - 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 + ## [7.0.18] - 2024-02-19 - Fixes vulnerabilities in dependencies diff --git a/build.gradle b/build.gradle index c6ee455fe..fcdfb140c 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "7.0.18" +version = "7.0.19" repositories { diff --git a/src/main/java/io/supertokens/ActiveUsers.java b/src/main/java/io/supertokens/ActiveUsers.java index 7c4601958..80a04abf6 100644 --- a/src/main/java/io/supertokens/ActiveUsers.java +++ b/src/main/java/io/supertokens/ActiveUsers.java @@ -1,10 +1,12 @@ package io.supertokens; +import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.sqlStorage.SQLStorage; import io.supertokens.storageLayer.StorageLayer; import org.jetbrains.annotations.TestOnly; @@ -33,6 +35,19 @@ public static int countUsersActiveSince(AppIdentifierWithStorage appIdentifierWi return appIdentifierWithStorage.getActiveUsersStorage().countUsersActiveSince(appIdentifierWithStorage, time); } + public static void updateLastActiveAfterLinking(AppIdentifierWithStorage appIdentifierWithStorage, + Main main, String primaryUserId, String recipeUserId) + throws StorageQueryException, TenantOrAppNotFoundException, StorageTransactionLogicException { + ActiveUsersStorage activeUsersStorage = appIdentifierWithStorage.getActiveUsersStorage(); + + ((SQLStorage) activeUsersStorage).startTransaction(con -> { + activeUsersStorage.deleteUserActive_Transaction(con, appIdentifierWithStorage, recipeUserId); + return null; + }); + + updateLastActive(appIdentifierWithStorage, main, primaryUserId); + } + @TestOnly public static int countUsersActiveSince(Main main, long time) throws StorageQueryException, TenantOrAppNotFoundException { diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 129adee22..d03c75f49 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); } @@ -2193,10 +2193,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 35ee2fd60..e3395f68b 100644 --- a/src/main/java/io/supertokens/session/Session.java +++ b/src/main/java/io/supertokens/session/Session.java @@ -46,6 +46,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; @@ -137,12 +139,29 @@ public static SessionInformationHolder createNewSession(TenantIdentifierWithStor } String primaryUserId = recipeUserId; - if (tenantIdentifierWithStorage.getStorage().getType().equals(STORAGE_TYPE.SQL)) { + + if (tenantIdentifierWithStorage.getStorage().getType() == STORAGE_TYPE.SQL) { + io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping.getUserIdMapping( + tenantIdentifierWithStorage.toAppIdentifierWithStorage(), recipeUserId, UserIdType.EXTERNAL); + if (userIdMapping != null) { + recipeUserId = userIdMapping.superTokensUserId; + } + primaryUserId = tenantIdentifierWithStorage.getAuthRecipeStorage() .getPrimaryUserIdStrForUserId(tenantIdentifierWithStorage.toAppIdentifier(), recipeUserId); if (primaryUserId == null) { primaryUserId = recipeUserId; } + + HashMap userIdMappings = UserIdMapping.getUserIdMappingForSuperTokensUserIds( + tenantIdentifierWithStorage.toAppIdentifierWithStorage(), + 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; @@ -377,7 +396,7 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M accessToken.sessionHandle, Utils.hashSHA256(accessToken.refreshTokenHash1), System.currentTimeMillis() + - config.getRefreshTokenValidity()); + config.getRefreshTokenValidity(), sessionInfo.useStaticKey); } storage.commitTransaction(con); @@ -454,7 +473,7 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M Utils.hashSHA256(accessToken.refreshTokenHash1), System.currentTimeMillis() + Config.getConfig(tenantIdentifierWithStorage, main) .getRefreshTokenValidity(), - sessionInfo.lastUpdatedSign); + sessionInfo.lastUpdatedSign, sessionInfo.useStaticKey); if (!success) { continue; } @@ -509,7 +528,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); } @@ -518,7 +537,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 { @@ -534,14 +554,15 @@ public static SessionInformationHolder refreshSession(AppIdentifier appIdentifie return refreshSessionHelper(refreshTokenInfo.tenantIdentifier.withStorage( StorageLayer.getStorage(refreshTokenInfo.tenantIdentifier, main)), - main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion); + main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion, shouldUseStaticKey); } private static SessionInformationHolder refreshSessionHelper( TenantIdentifierWithStorage tenantIdentifierWithStorage, 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 { @@ -566,7 +587,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 + storage.updateSessionInfo_Transaction(tenantIdentifierWithStorage, con, sessionHandle, + sessionInfo.refreshTokenHash2, sessionInfo.expiry, + useStaticKey); + } + // at this point, the input refresh token is the parent one. storage.commitTransaction(con); @@ -580,7 +610,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); @@ -600,13 +630,13 @@ private static SessionInformationHolder refreshSessionHelper( .equals(sessionInfo.refreshTokenHash2))) { storage.updateSessionInfo_Transaction(tenantIdentifierWithStorage, con, sessionHandle, Utils.hashSHA256(Utils.hashSHA256(refreshToken)), - System.currentTimeMillis() + config.getRefreshTokenValidity()); + System.currentTimeMillis() + config.getRefreshTokenValidity(), useStaticKey); storage.commitTransaction(con); return refreshSessionHelper(tenantIdentifierWithStorage, main, refreshToken, refreshTokenInfo, enableAntiCsrf, - accessTokenVersion); + accessTokenVersion, useStaticKey); } storage.commitTransaction(con); @@ -655,7 +685,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 = storage.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; @@ -666,7 +707,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); @@ -688,13 +729,13 @@ private static SessionInformationHolder refreshSessionHelper( Utils.hashSHA256(Utils.hashSHA256(refreshToken)), System.currentTimeMillis() + Config.getConfig(tenantIdentifierWithStorage, main).getRefreshTokenValidity(), - sessionInfo.lastUpdatedSign); + sessionInfo.lastUpdatedSign, useStaticKey); if (!success) { continue; } return refreshSessionHelper(tenantIdentifierWithStorage, 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 56a220152..1a0496b0c 100644 --- a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java +++ b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java @@ -323,7 +323,8 @@ public static HashMap getUserIdMappingForSuperTokensUserIds( ArrayList userIds) throws StorageQueryException { // userIds are already filtered for a tenant, so this becomes a tenant specific operation. - return tenantIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds(userIds); + return tenantIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds( + tenantIdentifierWithStorage.toAppIdentifier(), userIds); } public static HashMap getUserIdMappingForSuperTokensUserIds( @@ -331,7 +332,8 @@ public static HashMap getUserIdMappingForSuperTokensUserIds( ArrayList userIds) throws StorageQueryException { // userIds are already filtered for a tenant, so this becomes a tenant specific operation. - return appIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds(userIds); + return appIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds( + appIdentifierWithStorage, userIds); } @TestOnly 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 76a9d4dfe..4b4ccf338 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java @@ -106,6 +106,16 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I response.addProperty("status", "OK"); response.addProperty("accountsAlreadyLinked", linkAccountsResult.wasAlreadyLinked); response.add("user", linkAccountsResult.user.toJson()); + + if (!linkAccountsResult.wasAlreadyLinked) { + try { + ActiveUsers.updateLastActiveAfterLinking( + getPublicTenantStorage(req), main, primaryUserId, recipeUserId); + } catch (Exception e) { + // ignore + } + } + super.sendJsonResponse(200, response, resp); } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException e) { throw new ServletException(e); 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 1749e154b..bf659a4f3 100644 --- a/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java @@ -61,10 +61,14 @@ 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; @@ -75,13 +79,13 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I throw new ServletException(e); } - SemVer version = super.getVersionFromRequest(req); try { AccessToken.VERSION accessTokenVersion = AccessToken.getAccessTokenVersionForCDI(version); SessionInformationHolder sessionInfo = Session.refreshSession(appIdentifierWithStorage, main, refreshToken, antiCsrfToken, - enableAntiCsrf, accessTokenVersion); + enableAntiCsrf, accessTokenVersion, + useDynamicSigningKey == null ? null : Boolean.FALSE.equals(useDynamicSigningKey)); if (StorageLayer.getStorage(this.getTenantIdentifierWithStorageFromRequest(req), main).getType() == STORAGE_TYPE.SQL) { 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 8938c2700..5622ca6f4 100644 --- a/src/test/java/io/supertokens/test/accountlinking/SessionTests.java +++ b/src/test/java/io/supertokens/test/accountlinking/SessionTests.java @@ -598,4 +598,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 0e7f5e8d5..ee90f3fde 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; @@ -32,6 +34,7 @@ import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.STORAGE_TYPE; 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; @@ -39,6 +42,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; @@ -339,4 +343,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/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..fadb4f2d7 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